mimi-seed 0.5.0 → 0.7.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
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  mcpCall
4
- } from "./chunk-DW3LOLPJ.js";
4
+ } from "./chunk-QU2IDWJ2.js";
5
5
  import {
6
6
  CONFIG_LOCATION,
7
7
  CREDENTIALS,
8
8
  MCP_PKG,
9
9
  cmdDeploy,
10
10
  cmdSetup,
11
+ credLabel,
12
+ credNote,
11
13
  deleteConfig,
12
14
  detectAll,
13
15
  detectHints,
@@ -18,17 +20,38 @@ import {
18
20
  runMcpBin,
19
21
  tryCredById,
20
22
  writeConfig
21
- } from "./chunk-T757WVSG.js";
23
+ } from "./chunk-47TS72IP.js";
24
+ import {
25
+ catalog,
26
+ isLang,
27
+ resolveLang,
28
+ t,
29
+ writeSettings
30
+ } from "./chunk-SZQFVTEW.js";
22
31
 
23
32
  // src/index.ts
24
33
  import os3 from "os";
25
34
  import fs5 from "fs";
26
35
  import path5 from "path";
27
- import kleur8 from "kleur";
36
+ import kleur9 from "kleur";
28
37
  import open from "open";
29
38
 
30
39
  // src/handshake.ts
31
40
  import http from "http";
41
+ var M = catalog(
42
+ {
43
+ htmlLang: "ko",
44
+ connected: "\u2713 Mimi Seed \uC5F0\uACB0 \uC644\uB8CC",
45
+ backToTerminal: "\uD130\uBBF8\uB110\uB85C \uB3CC\uC544\uAC00\uC138\uC694.",
46
+ timeout: (seconds) => `${seconds}\uCD08 \uC548\uC5D0 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
47
+ },
48
+ {
49
+ htmlLang: "en",
50
+ connected: "\u2713 Mimi Seed connected",
51
+ backToTerminal: "You can go back to your terminal.",
52
+ timeout: (seconds) => `Not connected within ${seconds}s.`
53
+ }
54
+ );
32
55
  async function awaitHandshake(timeoutMs) {
33
56
  let resolve;
34
57
  let reject;
@@ -57,9 +80,9 @@ async function awaitHandshake(timeoutMs) {
57
80
  }
58
81
  res.setHeader("Content-Type", "text/html; charset=utf-8");
59
82
  res.end(`<!doctype html>
60
- <html lang="ko"><body style="font-family:system-ui;padding:40px;max-width:480px;margin:auto">
61
- <h2>\u2713 Mimi Seed \uC5F0\uACB0 \uC644\uB8CC</h2>
62
- <p>\uD130\uBBF8\uB110\uB85C \uB3CC\uC544\uAC00\uC138\uC694.</p>
83
+ <html lang="${M().htmlLang}"><body style="font-family:system-ui;padding:40px;max-width:480px;margin:auto">
84
+ <h2>${M().connected}</h2>
85
+ <p>${M().backToTerminal}</p>
63
86
  <script>setTimeout(()=>window.close(),1000)</script>
64
87
  </body></html>`);
65
88
  resolve({ token, prefix });
@@ -70,7 +93,7 @@ async function awaitHandshake(timeoutMs) {
70
93
  const port = server.address().port;
71
94
  const timer = setTimeout(() => {
72
95
  server.close();
73
- reject(new Error(`${timeoutMs / 1e3}\uCD08 \uC548\uC5D0 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`));
96
+ reject(new Error(M().timeout(timeoutMs / 1e3)));
74
97
  }, timeoutMs);
75
98
  promise.finally(() => clearTimeout(timer));
76
99
  return { port, promise };
@@ -179,78 +202,81 @@ function manifestDetail(id, svc) {
179
202
  }
180
203
  async function cmdDoctor() {
181
204
  const cwd = process.cwd();
182
- process.stdout.write(kleur.bold("mimi-seed doctor\n\n"));
183
- section("\uC778\uC99D");
205
+ const m = t().doctor;
206
+ process.stdout.write(kleur.bold(m.title + "\n\n"));
207
+ section(m.secAuth);
184
208
  const cfg = await getEffectiveConfig();
185
209
  if (!cfg) {
186
- fail("Mimi Seed \uD1A0\uD070 \uC5C6\uC74C", "`mimi-seed init` \uC2E4\uD589 \uD544\uC694");
210
+ fail(m.noToken, m.noTokenFix);
187
211
  } else {
188
- ok("\uD1A0\uD070 \uC800\uC7A5\uB428", `${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
189
- ok("\uC5D4\uB4DC\uD3EC\uC778\uD2B8", cfg.endpoint);
212
+ ok(m.tokenSaved, `${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
213
+ ok(m.endpoint, cfg.endpoint);
190
214
  if (process.env.MIMI_SEED_TOKEN) {
191
- ok("CI \uBAA8\uB4DC", "MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218 \uC0AC\uC6A9 \uC911");
215
+ ok(m.ciMode, m.ciModeDetail);
192
216
  }
193
217
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
194
218
  if (r.isError) {
195
- fail("\uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328", r.text.slice(0, 80));
219
+ fail(m.tokenInvalid, r.text.slice(0, 80));
196
220
  } else {
197
221
  const lines = r.text.split("\n").filter(Boolean);
198
- ok("Mimi Seed \uC11C\uBC84 \uC5F0\uACB0\uB428", `\uC571 ${lines.length}\uAC1C`);
222
+ ok(m.serverOk, m.appCount(lines.length));
199
223
  }
200
224
  }
201
- section("\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 (~/.mimi-seed)");
225
+ section(m.secCreds);
202
226
  migrateLegacyJenkins();
203
227
  const detected = detectAll();
204
228
  for (const spec of CREDENTIALS) {
205
229
  const d = detected.get(spec.id);
206
- const label = spec.note ? `${spec.label} (${spec.note})` : spec.label;
207
- if (d.present) ok(spec.label, d.detail);
208
- else if (isSatisfied(spec, detected)) warn(label, "\uD3F4\uBC31\uC73C\uB85C \uB3D9\uC791 \uC911");
230
+ const base = credLabel(spec);
231
+ const note = credNote(spec);
232
+ const label = note ? `${base} (${note})` : base;
233
+ if (d.present) ok(base, d.detail);
234
+ else if (isSatisfied(spec, detected)) warn(label, t().setup.fallbackWorking);
209
235
  else if (spec.requirement === "optional") warn(`${label}`, `\u2192 ${spec.fix}`);
210
- else fail(spec.label, `\u2192 ${spec.fix}`);
236
+ else fail(base, `\u2192 ${spec.fix}`);
211
237
  }
212
- process.stdout.write(kleur.dim(" \uC804\uBD80 \uC5F0\uACB0\uD558\uAE30: mimi-seed setup \xB7 OAuth \uC2E0\uC120\uB3C4: mimi-seed auth status\n"));
238
+ process.stdout.write(kleur.dim(m.credsHint));
213
239
  const loaded = findProjectManifest(cwd);
214
240
  if (loaded) {
215
- const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? "\uC774 \uD504\uB85C\uC81D\uD2B8";
216
- section(`${projName} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`);
241
+ const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? m.thisProject;
242
+ section(m.requirements(projName));
217
243
  for (const [id, svc] of manifestServiceEntries(loaded.manifest)) {
218
244
  const required = svc.required !== false;
219
245
  const detail = manifestDetail(id, svc);
220
246
  const spec = tryCredById(id);
221
247
  if (!spec) {
222
- warn(`${id} (\uC54C \uC218 \uC5C6\uB294 \uC11C\uBE44\uC2A4)`, svc.note ?? detail);
248
+ warn(m.unknownService(id), svc.note ?? detail);
223
249
  continue;
224
250
  }
225
251
  const connected = isSatisfied(spec, detected);
226
252
  if (connected) ok(id, detail);
227
- else if (!required) warn(`${id} (\uC120\uD0DD)`, svc.note ?? detail);
253
+ else if (!required) warn(`${id} (${t().common.optional})`, svc.note ?? detail);
228
254
  else fail(id, `\u2192 ${spec.fix}${detail ? " " + detail : ""}`);
229
255
  }
230
256
  }
231
- section("\uB85C\uCEEC \uD658\uACBD");
257
+ section(m.secEnv);
232
258
  const nodeVer = process.version;
233
259
  const [, major] = nodeVer.match(/v(\d+)/) ?? [];
234
260
  if (Number(major) >= 20) {
235
261
  ok("Node.js", nodeVer);
236
262
  } else {
237
- fail("Node.js", `${nodeVer} \u2014 v20 \uC774\uC0C1 \uD544\uC694 (.nvmrc \uCC38\uACE0)`);
263
+ fail("Node.js", m.nodeTooOld(nodeVer));
238
264
  }
239
265
  if (isGitRepo(cwd)) {
240
266
  const latestTag = getLatestTag(cwd);
241
267
  const commits = getGitLog(cwd, { limit: 5 });
242
- ok("Git \uC800\uC7A5\uC18C", latestTag ? `\uCD5C\uC2E0 \uD0DC\uADF8: ${latestTag}` : `\uCEE4\uBC0B ${commits.length}\uAC1C`);
268
+ ok(m.gitRepo, latestTag ? m.gitTag(latestTag) : m.gitCommits(commits.length));
243
269
  } else {
244
- warn("Git \uC800\uC7A5\uC18C \uC5C6\uC74C", "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00");
270
+ warn(m.noGit, m.noGitDetail);
245
271
  }
246
- section("\uC571 \uAC10\uC9C0");
272
+ section(m.secApps);
247
273
  const hints = await detectHints(cwd);
248
274
  if (hints.length === 0) {
249
- warn("\uC571 \uAC10\uC9C0 \uC5C6\uC74C", "app.json / build.gradle / Info.plist \uC5C6\uC74C");
275
+ warn(m.noApp, m.noAppDetail);
250
276
  } else {
251
277
  for (const h of hints) {
252
278
  const ids = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
253
- ok(h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)", ids);
279
+ ok(h.name ?? m.unnamed, ids);
254
280
  }
255
281
  }
256
282
  process.stdout.write("\n");
@@ -258,6 +284,70 @@ async function cmdDoctor() {
258
284
 
259
285
  // src/check.ts
260
286
  import kleur2 from "kleur";
287
+ var M2 = catalog(
288
+ {
289
+ noAccount: "\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589.\n",
290
+ title: "mimi-seed check \u2014 \uCD9C\uC2DC \uC804 \uC810\uAC80\n\n",
291
+ appsFailed: (msg) => `\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${msg}
292
+ `,
293
+ noApps: "\uB4F1\uB85D\uB41C \uC571\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uD6C4 \uC571\uC744 \uB4F1\uB85D\uD558\uC138\uC694.\n",
294
+ app: (name) => `\uC571: ${name}
295
+
296
+ `,
297
+ appsParseFailed: (raw) => `\uC571 \uBAA9\uB85D \uD30C\uC2F1 \uC2E4\uD328: ${raw}
298
+ `,
299
+ scoring: "\u{1F4CA} Readiness \uC810\uC218 \uACC4\uC0B0 \uC911...\n",
300
+ scoreFailed: (msg) => `\uC810\uC218 \uC870\uD68C \uC2E4\uD328: ${msg}
301
+ `,
302
+ score: (bar) => `
303
+ \uC810\uC218: ${bar}
304
+
305
+ `,
306
+ byModule: "\u2500\u2500 \uBAA8\uB4C8\uBCC4 \u2500\u2500\n",
307
+ blockers: "\u{1F6AB} \uBE14\uB85C\uCEE4:\n",
308
+ warnings: "\u26A0 \uACBD\uACE0:\n",
309
+ stepIntegration: (url) => `\uC5F0\uACB0 \uC9C4\uB2E8 ${url}`,
310
+ stepCopy: (url) => `\uBB38\uAD6C \uBCF4\uAC15 mimi-seed notes \uB610\uB294 ${url}`,
311
+ stepScreenshot: (url) => `\uC2A4\uD06C\uB9B0\uC0F7 ${url}`,
312
+ stepChecklist: (url) => `\uCCB4\uD06C\uB9AC\uC2A4\uD2B8 ${url}`,
313
+ nextSteps: "\u2192 \uB2E4\uC74C \uB2E8\uACC4:\n",
314
+ preview: " \uBBF8\uB9AC\uBCF4\uAE30: mimi-seed deploy --dry-run\n\n",
315
+ ready: "\u2713 \uCD9C\uC2DC \uC900\uBE44 \uC644\uB8CC!\n",
316
+ notReady: (score) => `\uC544\uC9C1 ${score}/100 \u2014 \uC704 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uC9C4\uD589\uD558\uC138\uC694.
317
+ `
318
+ },
319
+ {
320
+ noAccount: "No account connected. Run `mimi-seed init`.\n",
321
+ title: "mimi-seed check \u2014 pre-launch check\n\n",
322
+ appsFailed: (msg) => `Failed to list apps: ${msg}
323
+ `,
324
+ noApps: "No apps registered. Run `mimi-seed init`, then register an app.\n",
325
+ app: (name) => `App: ${name}
326
+
327
+ `,
328
+ appsParseFailed: (raw) => `Failed to parse the app list: ${raw}
329
+ `,
330
+ scoring: "\u{1F4CA} Computing the readiness score...\n",
331
+ scoreFailed: (msg) => `Failed to fetch the score: ${msg}
332
+ `,
333
+ score: (bar) => `
334
+ Score: ${bar}
335
+
336
+ `,
337
+ byModule: "\u2500\u2500 By module \u2500\u2500\n",
338
+ blockers: "\u{1F6AB} Blockers:\n",
339
+ warnings: "\u26A0 Warnings:\n",
340
+ stepIntegration: (url) => `Connections ${url}`,
341
+ stepCopy: (url) => `Copy mimi-seed notes or ${url}`,
342
+ stepScreenshot: (url) => `Screenshots ${url}`,
343
+ stepChecklist: (url) => `Checklist ${url}`,
344
+ nextSteps: "\u2192 Next steps:\n",
345
+ preview: " Preview: mimi-seed deploy --dry-run\n\n",
346
+ ready: "\u2713 Ready to launch!\n",
347
+ notReady: (score) => `Still ${score}/100 \u2014 work through the next steps above.
348
+ `
349
+ }
350
+ );
261
351
  function parseArgs(argv) {
262
352
  const args = { failOnBlocker: false };
263
353
  for (let i = 0; i < argv.length; i++) {
@@ -282,14 +372,13 @@ async function cmdCheck(argv) {
282
372
  const args = parseArgs(argv);
283
373
  const cfg = await getEffectiveConfig();
284
374
  if (!cfg) {
285
- process.stdout.write(kleur2.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589.\n"));
375
+ process.stdout.write(kleur2.red(M2().noAccount));
286
376
  process.exit(1);
287
377
  }
288
- process.stdout.write(kleur2.bold("mimi-seed check \u2014 \uCD9C\uC2DC \uC804 \uC810\uAC80\n\n"));
378
+ process.stdout.write(kleur2.bold(M2().title));
289
379
  const appsResult = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
290
380
  if (appsResult.isError) {
291
- process.stdout.write(kleur2.red(`\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${appsResult.text}
292
- `));
381
+ process.stdout.write(kleur2.red(M2().appsFailed(appsResult.text)));
293
382
  process.exit(1);
294
383
  }
295
384
  let appId = args.appId;
@@ -297,36 +386,29 @@ async function cmdCheck(argv) {
297
386
  try {
298
387
  const apps = JSON.parse(appsResult.text);
299
388
  if (!Array.isArray(apps) || apps.length === 0) {
300
- process.stdout.write(kleur2.yellow("\uB4F1\uB85D\uB41C \uC571\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uD6C4 \uC571\uC744 \uB4F1\uB85D\uD558\uC138\uC694.\n"));
389
+ process.stdout.write(kleur2.yellow(M2().noApps));
301
390
  process.exit(0);
302
391
  }
303
392
  appId = apps[0].id;
304
- process.stdout.write(kleur2.dim(`\uC571: ${apps[0].name ?? appId}
305
-
306
- `));
393
+ process.stdout.write(kleur2.dim(M2().app(apps[0].name ?? appId)));
307
394
  } catch {
308
- process.stdout.write(kleur2.red(`\uC571 \uBAA9\uB85D \uD30C\uC2F1 \uC2E4\uD328: ${appsResult.text.slice(0, 80)}
309
- `));
395
+ process.stdout.write(kleur2.red(M2().appsParseFailed(appsResult.text.slice(0, 80))));
310
396
  process.exit(1);
311
397
  }
312
398
  }
313
- process.stdout.write("\u{1F4CA} Readiness \uC810\uC218 \uACC4\uC0B0 \uC911...\n");
399
+ process.stdout.write(M2().scoring);
314
400
  const readinessResult = await mcpCall(cfg.endpoint, cfg.token, "get_readiness", { app_id: appId });
315
401
  if (readinessResult.isError) {
316
- process.stdout.write(kleur2.red(`\uC810\uC218 \uC870\uD68C \uC2E4\uD328: ${readinessResult.text}
317
- `));
402
+ process.stdout.write(kleur2.red(M2().scoreFailed(readinessResult.text)));
318
403
  process.exit(1);
319
404
  }
320
405
  let hasBlocker = false;
321
406
  try {
322
407
  const data = JSON.parse(readinessResult.text);
323
408
  const score = data.score ?? 0;
324
- process.stdout.write(`
325
- \uC810\uC218: ${renderScore(score)}
326
-
327
- `);
409
+ process.stdout.write(M2().score(renderScore(score)));
328
410
  if (data.modules) {
329
- process.stdout.write(kleur2.dim("\u2500\u2500 \uBAA8\uB4C8\uBCC4 \u2500\u2500\n"));
411
+ process.stdout.write(kleur2.dim(M2().byModule));
330
412
  for (const [key, val] of Object.entries(data.modules)) {
331
413
  const label = MODULE_LABELS[key] ?? key;
332
414
  const color = val >= 25 ? kleur2.green : val >= 10 ? kleur2.yellow : kleur2.red;
@@ -337,13 +419,13 @@ async function cmdCheck(argv) {
337
419
  }
338
420
  if (data.blockers?.length) {
339
421
  hasBlocker = true;
340
- process.stdout.write(kleur2.bold("\u{1F6AB} \uBE14\uB85C\uCEE4:\n"));
422
+ process.stdout.write(kleur2.bold(M2().blockers));
341
423
  for (const b of data.blockers) process.stdout.write(` ${kleur2.red("\u2022")} ${b}
342
424
  `);
343
425
  process.stdout.write("\n");
344
426
  }
345
427
  if (data.warnings?.length) {
346
- process.stdout.write(kleur2.bold("\u26A0 \uACBD\uACE0:\n"));
428
+ process.stdout.write(kleur2.bold(M2().warnings));
347
429
  for (const w of data.warnings) process.stdout.write(` ${kleur2.yellow("\u2022")} ${w}
348
430
  `);
349
431
  process.stdout.write("\n");
@@ -351,22 +433,19 @@ async function cmdCheck(argv) {
351
433
  const base = `${cfg.webBase}/apps/${appId}`;
352
434
  const nextSteps = [];
353
435
  if (data.modules) {
354
- if ((data.modules.integration ?? 25) < 25) nextSteps.push(`\uC5F0\uACB0 \uC9C4\uB2E8 ${base}/integration`);
355
- if ((data.modules.copy ?? 25) < 25) nextSteps.push(`\uBB38\uAD6C \uBCF4\uAC15 mimi-seed notes \uB610\uB294 ${base}/copy`);
356
- if ((data.modules.screenshot ?? 25) < 25) nextSteps.push(`\uC2A4\uD06C\uB9B0\uC0F7 ${base}/screenshots`);
357
- if ((data.modules.checklist ?? 25) < 25) nextSteps.push(`\uCCB4\uD06C\uB9AC\uC2A4\uD2B8 ${base}/launch`);
436
+ if ((data.modules.integration ?? 25) < 25) nextSteps.push(M2().stepIntegration(`${base}/integration`));
437
+ if ((data.modules.copy ?? 25) < 25) nextSteps.push(M2().stepCopy(`${base}/copy`));
438
+ if ((data.modules.screenshot ?? 25) < 25) nextSteps.push(M2().stepScreenshot(`${base}/screenshots`));
439
+ if ((data.modules.checklist ?? 25) < 25) nextSteps.push(M2().stepChecklist(`${base}/launch`));
358
440
  }
359
441
  if (nextSteps.length) {
360
- process.stdout.write(kleur2.bold("\u2192 \uB2E4\uC74C \uB2E8\uACC4:\n"));
442
+ process.stdout.write(kleur2.bold(M2().nextSteps));
361
443
  for (const s of nextSteps) process.stdout.write(` ${kleur2.cyan("\u2022")} ${s}
362
444
  `);
363
- process.stdout.write(kleur2.dim(` \uBBF8\uB9AC\uBCF4\uAE30: mimi-seed deploy --dry-run
364
-
365
- `));
445
+ process.stdout.write(kleur2.dim(M2().preview));
366
446
  }
367
447
  if (!hasBlocker) {
368
- process.stdout.write(score >= 80 ? kleur2.green("\u2713 \uCD9C\uC2DC \uC900\uBE44 \uC644\uB8CC!\n") : kleur2.yellow(`\uC544\uC9C1 ${score}/100 \u2014 \uC704 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uC9C4\uD589\uD558\uC138\uC694.
369
- `));
448
+ process.stdout.write(score >= 80 ? kleur2.green(M2().ready) : kleur2.yellow(M2().notReady(score)));
370
449
  }
371
450
  } catch {
372
451
  for (const line of readinessResult.text.split("\n")) process.stdout.write(" " + line + "\n");
@@ -378,6 +457,122 @@ async function cmdCheck(argv) {
378
457
  import { createInterface } from "readline/promises";
379
458
  import Anthropic from "@anthropic-ai/sdk";
380
459
  import kleur3 from "kleur";
460
+ var M3 = catalog(
461
+ {
462
+ // Claude 프롬프트
463
+ localeHint: (l) => `"${l}": "\uD574\uB2F9 \uC5B8\uC5B4\uB85C \uBC88\uC5ED\uB41C \uAC04\uACB0\uD55C \uBC84\uC804"`,
464
+ system: "\uC571 \uC2A4\uD1A0\uC5B4 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC804\uBB38 \uCE74\uD53C\uB77C\uC774\uD130\uC785\uB2C8\uB2E4. \uCEE4\uBC0B \uB0B4\uC5ED\uC744 \uC0AC\uC6A9\uC790 \uCE5C\uD654\uC801\uC778 \uC5B8\uC5B4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. \uD56D\uC0C1 \uC720\uD6A8\uD55C JSON\uC73C\uB85C\uB9CC \uC751\uB2F5\uD558\uC138\uC694.",
465
+ userPrompt: (commitsText, localeList) => `\uB2E4\uC74C \uCEE4\uBC0B \uB0B4\uC5ED\uC73C\uB85C \uB9B4\uB9AC\uC988 \uB178\uD2B8\uB97C 3\uAC00\uC9C0 \uD1A4\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694:
466
+
467
+ ${commitsText}
468
+
469
+ JSON:
470
+ {
471
+ "concise": "\uAC04\uACB0\uD55C \uBC84\uC804 (3\uC904 \uC774\uB0B4, \uBD88\uB9BF)",
472
+ "detailed": "\uC0C1\uC138 \uBC84\uC804 (5\uAC1C \uC774\uB0B4, \uBD88\uB9BF)",
473
+ "marketing": "\uB9C8\uCF00\uD305 \uBC84\uC804 (\uC5F4\uC815\uC801 \uD1A4)",
474
+ "localized": {
475
+ ${localeList}
476
+ }
477
+ }`,
478
+ parseFailed: "AI \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328",
479
+ // 템플릿 폴백
480
+ marketingTemplate: (items) => `\uC0C8\uB85C\uC6B4 \uC5C5\uB370\uC774\uD2B8\uAC00 \uC900\uBE44\uB410\uC2B5\uB2C8\uB2E4!
481
+
482
+ ${items}
483
+
484
+ \uC9C0\uAE08 \uBC14\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.`,
485
+ // cmdNotes
486
+ title: "mimi-seed notes \u2014 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131\n\n",
487
+ notGitRepo: "Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.\n",
488
+ range: (from, to) => `\uBC94\uC704: ${from} \u2192 ${to}
489
+ `,
490
+ recentCommits: (limit) => `\uCD5C\uADFC ${limit}\uAC1C \uCEE4\uBC0B
491
+ `,
492
+ noCommits: "\uCEE4\uBC0B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n",
493
+ analyzing: (n) => `\uCEE4\uBC0B ${n}\uAC1C \uBD84\uC11D \uC911...
494
+
495
+ `,
496
+ generating: "\u{1F916} Claude AI\uB85C \uC0DD\uC131 \uC911...\n",
497
+ aiFailed: (msg) => `AI \uC0DD\uC131 \uC2E4\uD328, \uD15C\uD50C\uB9BF \uC0AC\uC6A9: ${msg}
498
+ `,
499
+ noApiKey: "ANTHROPIC_API_KEY \uC5C6\uC74C \u2014 \uC790\uB3D9 \uD3EC\uB9F7\uD305 \uC0AC\uC6A9\n",
500
+ noApiKeyHint: "AI \uC0DD\uC131 \uD65C\uC131\uD654: export ANTHROPIC_API_KEY=sk-ant-...\n\n",
501
+ hdrConcise: "\u2500\u2500\u2500 \uAC04\uACB0\uD55C \uBC84\uC804 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
502
+ hdrDetailed: "\u2500\u2500\u2500 \uC0C1\uC138 \uBC84\uC804 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
503
+ hdrMarketing: "\u2500\u2500\u2500 \uB9C8\uCF00\uD305 \uBC84\uC804 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
504
+ hdrLocalized: "\u2500\u2500\u2500 \uB2E4\uAD6D\uC5B4 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
505
+ needAccount: "Mimi Seed \uACC4\uC815 \uC5F0\uACB0 \uD544\uC694. `mimi-seed init` \uC2E4\uD589.\n",
506
+ choosePrompt: "\uC801\uC6A9\uD560 \uBC84\uC804 [1=\uAC04\uACB0/2=\uC0C1\uC138/3=\uB9C8\uCF00\uD305/Enter=\uAC74\uB108\uB700]: ",
507
+ skipped: "\uAC74\uB108\uB700.\n",
508
+ listAppsFailed: (msg) => `\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${msg}
509
+ `,
510
+ noApps: "\uB4F1\uB85D\uB41C \uC571\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n",
511
+ toneToAllLocales: "\uC120\uD0DD\uD55C \uD1A4\uC744 \uBAA8\uB4E0 \uB85C\uCF00\uC77C\uC5D0 \uC801\uC6A9\uD569\uB2C8\uB2E4 (\uC790\uB3D9 \uBC88\uC5ED \uBBF8\uC801\uC6A9).\n",
512
+ applying: "Play Store\uC5D0 \uC801\uC6A9 \uC911...\n",
513
+ applyFailed: (locale, msg) => `${locale} \uC801\uC6A9 \uC2E4\uD328: ${msg}
514
+ `,
515
+ applied: (locale) => `\u2713 ${locale} \uC801\uC6A9\uB428
516
+ `
517
+ },
518
+ {
519
+ // Claude prompt
520
+ localeHint: (l) => `"${l}": "the concise version, translated into that language"`,
521
+ system: "You are an expert app store release-notes copywriter. You turn commit history into user-friendly language. Always respond with valid JSON only.",
522
+ userPrompt: (commitsText, localeList) => `Write release notes in 3 tones from the following commit history:
523
+
524
+ ${commitsText}
525
+
526
+ JSON:
527
+ {
528
+ "concise": "concise version (3 bullets max)",
529
+ "detailed": "detailed version (5 bullets max)",
530
+ "marketing": "marketing version (enthusiastic tone)",
531
+ "localized": {
532
+ ${localeList}
533
+ }
534
+ }`,
535
+ parseFailed: "Failed to parse the AI response",
536
+ // Template fallback
537
+ marketingTemplate: (items) => `A new update is here!
538
+
539
+ ${items}
540
+
541
+ Update now.`,
542
+ // cmdNotes
543
+ title: "mimi-seed notes \u2014 generate release notes\n\n",
544
+ notGitRepo: "Not a git repository.\n",
545
+ range: (from, to) => `Range: ${from} \u2192 ${to}
546
+ `,
547
+ recentCommits: (limit) => `Last ${limit} commit(s)
548
+ `,
549
+ noCommits: "No commits found.\n",
550
+ analyzing: (n) => `Analyzing ${n} commit(s)...
551
+
552
+ `,
553
+ generating: "\u{1F916} Generating with Claude AI...\n",
554
+ aiFailed: (msg) => `AI generation failed, falling back to the template: ${msg}
555
+ `,
556
+ noApiKey: "No ANTHROPIC_API_KEY \u2014 using automatic formatting\n",
557
+ noApiKeyHint: "Enable AI generation: export ANTHROPIC_API_KEY=sk-ant-...\n\n",
558
+ hdrConcise: "\u2500\u2500\u2500 Concise \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
559
+ hdrDetailed: "\u2500\u2500\u2500 Detailed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
560
+ hdrMarketing: "\u2500\u2500\u2500 Marketing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
561
+ hdrLocalized: "\u2500\u2500\u2500 Localized \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
562
+ needAccount: "A Mimi Seed account is required. Run `mimi-seed init`.\n",
563
+ choosePrompt: "Which version? [1=concise/2=detailed/3=marketing/Enter=skip]: ",
564
+ skipped: "Skipped.\n",
565
+ listAppsFailed: (msg) => `Failed to list apps: ${msg}
566
+ `,
567
+ noApps: "No apps registered.\n",
568
+ toneToAllLocales: "Applying the selected tone to every locale (no automatic translation).\n",
569
+ applying: "Applying to Play Store...\n",
570
+ applyFailed: (locale, msg) => `${locale} failed to apply: ${msg}
571
+ `,
572
+ applied: (locale) => `\u2713 ${locale} applied
573
+ `
574
+ }
575
+ );
381
576
  function parseArgs2(argv) {
382
577
  const args = { to: "HEAD", locales: ["ko", "en-US"], apply: false, noInteractive: false, limit: 30 };
383
578
  for (let i = 0; i < argv.length; i++) {
@@ -398,31 +593,19 @@ async function promptUser(question) {
398
593
  }
399
594
  async function generateWithClaude(commitsText, locales) {
400
595
  const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
401
- const localeList = locales.map((l) => `"${l}": "\uD574\uB2F9 \uC5B8\uC5B4\uB85C \uBC88\uC5ED\uB41C \uAC04\uACB0\uD55C \uBC84\uC804"`).join(",\n ");
596
+ const localeList = locales.map((l) => M3().localeHint(l)).join(",\n ");
402
597
  const response = await client.messages.create({
403
598
  model: "claude-haiku-4-5-20251001",
404
599
  max_tokens: 1500,
405
- system: "\uC571 \uC2A4\uD1A0\uC5B4 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC804\uBB38 \uCE74\uD53C\uB77C\uC774\uD130\uC785\uB2C8\uB2E4. \uCEE4\uBC0B \uB0B4\uC5ED\uC744 \uC0AC\uC6A9\uC790 \uCE5C\uD654\uC801\uC778 \uC5B8\uC5B4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. \uD56D\uC0C1 \uC720\uD6A8\uD55C JSON\uC73C\uB85C\uB9CC \uC751\uB2F5\uD558\uC138\uC694.",
600
+ system: M3().system,
406
601
  messages: [{
407
602
  role: "user",
408
- content: `\uB2E4\uC74C \uCEE4\uBC0B \uB0B4\uC5ED\uC73C\uB85C \uB9B4\uB9AC\uC988 \uB178\uD2B8\uB97C 3\uAC00\uC9C0 \uD1A4\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694:
409
-
410
- ${commitsText}
411
-
412
- JSON:
413
- {
414
- "concise": "\uAC04\uACB0\uD55C \uBC84\uC804 (3\uC904 \uC774\uB0B4, \uBD88\uB9BF)",
415
- "detailed": "\uC0C1\uC138 \uBC84\uC804 (5\uAC1C \uC774\uB0B4, \uBD88\uB9BF)",
416
- "marketing": "\uB9C8\uCF00\uD305 \uBC84\uC804 (\uC5F4\uC815\uC801 \uD1A4)",
417
- "localized": {
418
- ${localeList}
419
- }
420
- }`
603
+ content: M3().userPrompt(commitsText, localeList)
421
604
  }]
422
605
  });
423
606
  const text = response.content[0].type === "text" ? response.content[0].text : "";
424
607
  const match = text.match(/\{[\s\S]*\}/);
425
- if (!match) throw new Error("AI \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328");
608
+ if (!match) throw new Error(M3().parseFailed);
426
609
  return JSON.parse(match[0]);
427
610
  }
428
611
  function generateTemplate(commits, locales) {
@@ -432,11 +615,7 @@ function generateTemplate(commits, locales) {
432
615
  });
433
616
  const concise = items.slice(0, 3).join("\n");
434
617
  const detailed = items.join("\n");
435
- const marketing = `\uC0C8\uB85C\uC6B4 \uC5C5\uB370\uC774\uD2B8\uAC00 \uC900\uBE44\uB410\uC2B5\uB2C8\uB2E4!
436
-
437
- ${items.slice(0, 5).join("\n")}
438
-
439
- \uC9C0\uAE08 \uBC14\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.`;
618
+ const marketing = M3().marketingTemplate(items.slice(0, 5).join("\n"));
440
619
  const localized = Object.fromEntries(locales.map((l) => [l, concise]));
441
620
  return { concise, detailed, marketing, localized };
442
621
  }
@@ -451,46 +630,43 @@ function parseFirstApp(text) {
451
630
  async function cmdNotes(argv) {
452
631
  const args = parseArgs2(argv);
453
632
  const cwd = process.cwd();
454
- process.stdout.write(kleur3.bold("mimi-seed notes \u2014 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131\n\n"));
633
+ process.stdout.write(kleur3.bold(M3().title));
455
634
  if (!isGitRepo(cwd)) {
456
- process.stdout.write(kleur3.red("Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.\n"));
635
+ process.stdout.write(kleur3.red(M3().notGitRepo));
457
636
  process.exit(1);
458
637
  }
459
638
  const latestTag = getLatestTag(cwd);
460
639
  const fromRef = args.from ?? latestTag ?? void 0;
461
- process.stdout.write(kleur3.dim(fromRef ? `\uBC94\uC704: ${fromRef} \u2192 ${args.to}
462
- ` : `\uCD5C\uADFC ${args.limit}\uAC1C \uCEE4\uBC0B
463
- `));
640
+ process.stdout.write(
641
+ kleur3.dim(fromRef ? M3().range(fromRef, args.to) : M3().recentCommits(args.limit))
642
+ );
464
643
  const commits = getGitLog(cwd, { from: fromRef, to: args.to, limit: args.limit });
465
644
  if (commits.length === 0) {
466
- process.stdout.write(kleur3.yellow("\uCEE4\uBC0B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
645
+ process.stdout.write(kleur3.yellow(M3().noCommits));
467
646
  process.exit(0);
468
647
  }
469
- process.stdout.write(kleur3.dim(`\uCEE4\uBC0B ${commits.length}\uAC1C \uBD84\uC11D \uC911...
470
-
471
- `));
648
+ process.stdout.write(kleur3.dim(M3().analyzing(commits.length)));
472
649
  let result;
473
650
  if (process.env.ANTHROPIC_API_KEY) {
474
- process.stdout.write("\u{1F916} Claude AI\uB85C \uC0DD\uC131 \uC911...\n");
651
+ process.stdout.write(M3().generating);
475
652
  try {
476
653
  result = await generateWithClaude(formatCommitsForPrompt(commits), args.locales);
477
654
  } catch (e) {
478
- process.stdout.write(kleur3.yellow(`AI \uC0DD\uC131 \uC2E4\uD328, \uD15C\uD50C\uB9BF \uC0AC\uC6A9: ${e.message}
479
- `));
655
+ process.stdout.write(kleur3.yellow(M3().aiFailed(e.message)));
480
656
  result = generateTemplate(commits, args.locales);
481
657
  }
482
658
  } else {
483
- process.stdout.write(kleur3.dim("ANTHROPIC_API_KEY \uC5C6\uC74C \u2014 \uC790\uB3D9 \uD3EC\uB9F7\uD305 \uC0AC\uC6A9\n") + kleur3.dim("AI \uC0DD\uC131 \uD65C\uC131\uD654: export ANTHROPIC_API_KEY=sk-ant-...\n\n"));
659
+ process.stdout.write(kleur3.dim(M3().noApiKey) + kleur3.dim(M3().noApiKeyHint));
484
660
  result = generateTemplate(commits, args.locales);
485
661
  }
486
- process.stdout.write(kleur3.bold("\u2500\u2500\u2500 \uAC04\uACB0\uD55C \uBC84\uC804 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
662
+ process.stdout.write(kleur3.bold(M3().hdrConcise));
487
663
  process.stdout.write(result.concise + "\n\n");
488
- process.stdout.write(kleur3.bold("\u2500\u2500\u2500 \uC0C1\uC138 \uBC84\uC804 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
664
+ process.stdout.write(kleur3.bold(M3().hdrDetailed));
489
665
  process.stdout.write(result.detailed + "\n\n");
490
- process.stdout.write(kleur3.bold("\u2500\u2500\u2500 \uB9C8\uCF00\uD305 \uBC84\uC804 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
666
+ process.stdout.write(kleur3.bold(M3().hdrMarketing));
491
667
  process.stdout.write(result.marketing + "\n\n");
492
668
  if (Object.keys(result.localized).length > 0) {
493
- process.stdout.write(kleur3.bold("\u2500\u2500\u2500 \uB2E4\uAD6D\uC5B4 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
669
+ process.stdout.write(kleur3.bold(M3().hdrLocalized));
494
670
  for (const [locale, text] of Object.entries(result.localized)) {
495
671
  process.stdout.write(kleur3.dim(`[${locale}]
496
672
  `) + text + "\n\n");
@@ -500,15 +676,15 @@ async function cmdNotes(argv) {
500
676
  if (!args.apply && !shouldPrompt) return;
501
677
  const cfg = await getEffectiveConfig();
502
678
  if (!cfg) {
503
- process.stdout.write(kleur3.yellow("Mimi Seed \uACC4\uC815 \uC5F0\uACB0 \uD544\uC694. `mimi-seed init` \uC2E4\uD589.\n"));
679
+ process.stdout.write(kleur3.yellow(M3().needAccount));
504
680
  return;
505
681
  }
506
682
  let selectedText = result.concise;
507
683
  let userSelectedTone = false;
508
684
  if (shouldPrompt) {
509
- const choice = await promptUser("\uC801\uC6A9\uD560 \uBC84\uC804 [1=\uAC04\uACB0/2=\uC0C1\uC138/3=\uB9C8\uCF00\uD305/Enter=\uAC74\uB108\uB700]: ");
685
+ const choice = await promptUser(M3().choosePrompt);
510
686
  if (!choice) {
511
- process.stdout.write(kleur3.dim("\uAC74\uB108\uB700.\n"));
687
+ process.stdout.write(kleur3.dim(M3().skipped));
512
688
  return;
513
689
  }
514
690
  if (choice === "2") selectedText = result.detailed;
@@ -517,19 +693,18 @@ async function cmdNotes(argv) {
517
693
  }
518
694
  const appsResult = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
519
695
  if (appsResult.isError) {
520
- process.stdout.write(kleur3.red(`\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${appsResult.text}
521
- `));
696
+ process.stdout.write(kleur3.red(M3().listAppsFailed(appsResult.text)));
522
697
  return;
523
698
  }
524
699
  const app = parseFirstApp(appsResult.text);
525
700
  if (!app) {
526
- process.stdout.write(kleur3.yellow("\uB4F1\uB85D\uB41C \uC571\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
701
+ process.stdout.write(kleur3.yellow(M3().noApps));
527
702
  return;
528
703
  }
529
704
  if (userSelectedTone && args.locales.length > 1) {
530
- process.stdout.write(kleur3.dim("\uC120\uD0DD\uD55C \uD1A4\uC744 \uBAA8\uB4E0 \uB85C\uCF00\uC77C\uC5D0 \uC801\uC6A9\uD569\uB2C8\uB2E4 (\uC790\uB3D9 \uBC88\uC5ED \uBBF8\uC801\uC6A9).\n"));
705
+ process.stdout.write(kleur3.dim(M3().toneToAllLocales));
531
706
  }
532
- process.stdout.write("Play Store\uC5D0 \uC801\uC6A9 \uC911...\n");
707
+ process.stdout.write(M3().applying);
533
708
  for (const locale of args.locales) {
534
709
  const text = userSelectedTone ? selectedText : result.localized[locale] ?? selectedText;
535
710
  const r = await mcpCall(cfg.endpoint, cfg.token, "apply_release_notes", {
@@ -538,9 +713,9 @@ async function cmdNotes(argv) {
538
713
  locale,
539
714
  text
540
715
  });
541
- process.stdout.write(r.isError ? kleur3.red(`${locale} \uC801\uC6A9 \uC2E4\uD328: ${r.text}
542
- `) : kleur3.green(`\u2713 ${locale} \uC801\uC6A9\uB428
543
- `));
716
+ process.stdout.write(
717
+ r.isError ? kleur3.red(M3().applyFailed(locale, r.text)) : kleur3.green(M3().applied(locale))
718
+ );
544
719
  }
545
720
  }
546
721
 
@@ -548,6 +723,120 @@ async function cmdNotes(argv) {
548
723
  import { createInterface as createInterface2 } from "readline/promises";
549
724
  import Anthropic2 from "@anthropic-ai/sdk";
550
725
  import kleur4 from "kleur";
726
+ var M4 = catalog(
727
+ {
728
+ toneGuides: {
729
+ friendly: "\uCE5C\uADFC\uD558\uACE0 \uB530\uB73B\uD558\uAC8C. \uC774\uBAA8\uC9C0 1~2\uAC1C \uC0AC\uC6A9. \uAC10\uC0AC \uC778\uC0AC\uB85C \uC2DC\uC791.",
730
+ professional: "\uC815\uC911\uD558\uACE0 \uACF5\uC2DD\uC801\uC73C\uB85C. \uBB38\uC81C\uB97C \uC778\uC815\uD558\uACE0 \uD574\uACB0\uCC45\uC744 \uC81C\uC2DC.",
731
+ empathetic: "\uACF5\uAC10\uC744 \uBA3C\uC800 \uD45C\uD604. \uBD88\uD3B8\uC744 \uCDA9\uBD84\uD788 \uC778\uC815\uD55C \uD6C4 \uD574\uACB0 \uC758\uC9C0\uB97C \uBCF4\uC5EC\uC90C.",
732
+ brief: "2~3\uBB38\uC7A5\uC73C\uB85C \uAC04\uACB0\uD558\uAC8C. \uD575\uC2EC \uC751\uB2F5\uB9CC."
733
+ },
734
+ sentimentPrompts: {
735
+ positive: "\uAE0D\uC815\uC801\uC778 \uB9AC\uBDF0. \uAC10\uC0AC \uC778\uC0AC\uC640 \uD568\uAED8 \uC55E\uC73C\uB85C\uB3C4 \uC88B\uC740 \uACBD\uD5D8\uC744 \uC81C\uACF5\uD558\uACA0\uB2E4\uB294 \uC758\uC9C0\uB97C \uD45C\uD604.",
736
+ negative: "\uBD80\uC815\uC801\uC778 \uB9AC\uBDF0. \uBA3C\uC800 \uBD88\uD3B8\uC744 \uC0AC\uACFC\uD558\uACE0, \uBB38\uC81C \uD574\uACB0 \uC758\uC9C0\uB97C \uBCF4\uC5EC\uC918. \uAC00\uB2A5\uD558\uBA74 \uC9C0\uC6D0 \uCC44\uB110\uB85C \uC720\uB3C4.",
737
+ neutral: "\uC911\uB9BD\uC801\uC778 \uB9AC\uBDF0. \uD53C\uB4DC\uBC31\uC5D0 \uAC10\uC0AC\uD558\uACE0 \uAC1C\uC120 \uB178\uB825\uC744 \uC57D\uC18D.",
738
+ bug_report: "\uBC84\uADF8 \uB9AC\uD3EC\uD2B8. \uBB38\uC81C\uB97C \uD655\uC778\uD588\uC74C\uC744 \uC54C\uB9AC\uACE0, \uC218\uC815 \uC608\uC815\uC784\uC744 \uC804\uB2EC.",
739
+ feature_request: "\uAE30\uB2A5 \uC694\uCCAD. \uD53C\uB4DC\uBC31\uC5D0 \uAC10\uC0AC\uD558\uACE0, \uAC80\uD1A0\uD558\uACA0\uB2E4\uACE0 \uC57D\uC18D."
740
+ },
741
+ localeNames: { ko: "\uD55C\uAD6D\uC5B4", en: "\uC601\uC5B4", ja: "\uC77C\uBCF8\uC5B4", "en-US": "\uC601\uC5B4", "zh-TW": "\uBC88\uCCB4\uC911\uAD6D\uC5B4" },
742
+ // Claude 프롬프트
743
+ starsLabel: (stars, rating) => `\uBCC4\uC810: ${stars} (${rating}/5)
744
+ `,
745
+ defaultDeveloper: "\uAC1C\uBC1C\uD300",
746
+ defaultApp: "\uC571",
747
+ system: (langName, toneGuide, developer) => `\uC571 \uAC1C\uBC1C\uC790\uB97C \uB300\uC2E0\uD574 \uC2A4\uD1A0\uC5B4 \uB9AC\uBDF0\uC5D0 \uB2F5\uBCC0\uD558\uB294 \uC804\uBB38\uAC00. ${langName}\uB85C 150\uC790 \uC774\uB0B4\uB85C \uB2F5\uBCC0. ${toneGuide} \uAC1C\uBC1C\uC790 \uC774\uB984: ${developer}`,
748
+ userPrompt: (app, stars, text, sentimentPrompt) => `\uC571: ${app}
749
+ ${stars}\uB9AC\uBDF0: "${text}"
750
+
751
+ \uC9C0\uCE68: ${sentimentPrompt}
752
+
753
+ JSON\uC73C\uB85C\uB9CC \uC751\uB2F5: { "reply": "\uB2F5\uBCC0 \uD14D\uC2A4\uD2B8" }`,
754
+ parseFailed: "AI \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328",
755
+ // cmdReview
756
+ title: "mimi-seed review \u2014 \uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\n\n",
757
+ noApiKey: "ANTHROPIC_API_KEY\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n",
758
+ noApiKeyHint: "\uD65C\uC131\uD654: export ANTHROPIC_API_KEY=sk-ant-...\n",
759
+ textRequired: "--text <\uB9AC\uBDF0 \uB0B4\uC6A9> \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n",
760
+ enterReview: "\uB9AC\uBDF0 \uB0B4\uC6A9\uC744 \uC785\uB825\uD558\uC138\uC694 (\uC5EC\uB7EC \uC904: Ctrl+D\uB85C \uC644\uB8CC):\n",
761
+ noInput: "\uC785\uB825 \uC5C6\uC74C. \uC885\uB8CC.\n",
762
+ meta: (stars, tone, language) => `\uBCC4\uC810: ${stars} tone: ${tone} \uC5B8\uC5B4: ${language}
763
+
764
+ `,
765
+ generating: "\u{1F916} Claude AI\uB85C \uB2F5\uBCC0 \uC0DD\uC131 \uC911...\n",
766
+ hdrReply: "\u2500\u2500\u2500 \uC81C\uC548 \uB2F5\uBCC0 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
767
+ draftWarning: "\n\u26A0 \uC774 \uB2F5\uBCC0\uC740 AI\uAC00 \uC0DD\uC131\uD55C \uCD08\uC548\uC785\uB2C8\uB2E4. \uAC8C\uC2DC \uC804 \uBC18\uB4DC\uC2DC \uAC80\uD1A0\uD558\uC138\uC694.\n\n",
768
+ postPrompt: "\uC774 \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC\uD560\uAE4C\uC694? [y/N]: ",
769
+ skipped: "\uAC74\uB108\uB700.\n",
770
+ needAccount: "Mimi Seed \uACC4\uC815 \uC5F0\uACB0 \uD544\uC694. `mimi-seed init` \uC2E4\uD589.\n",
771
+ idsRequired: "--review-id \uC640 --package-name \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n",
772
+ packagePrompt: "\uD328\uD0A4\uC9C0\uBA85 (\uC608: com.example.app): ",
773
+ reviewIdPrompt: "\uB9AC\uBDF0 ID: ",
774
+ notEnoughInfo: "\uC815\uBCF4 \uBD80\uC871. \uCDE8\uC18C.\n",
775
+ posting: "Play Store\uC5D0 \uAC8C\uC2DC \uC911...\n",
776
+ postFailed: (msg) => `\uAC8C\uC2DC \uC2E4\uD328: ${msg}
777
+ `,
778
+ posted: "\u2713 \uB2F5\uBCC0 \uAC8C\uC2DC \uC644\uB8CC\n"
779
+ },
780
+ {
781
+ toneGuides: {
782
+ friendly: "Warm and friendly. Use 1-2 emoji. Open with a thank-you.",
783
+ professional: "Polite and formal. Acknowledge the issue and offer a solution.",
784
+ empathetic: "Lead with empathy. Fully acknowledge the frustration, then show a commitment to fix it.",
785
+ brief: "Keep it to 2-3 sentences. Only the essential response."
786
+ },
787
+ sentimentPrompts: {
788
+ positive: "A positive review. Say thank you and commit to keeping the experience great.",
789
+ negative: "A negative review. Apologize for the trouble first, then show a commitment to fixing it. Point to a support channel if you can.",
790
+ neutral: "A neutral review. Thank them for the feedback and promise to keep improving.",
791
+ bug_report: "A bug report. Confirm the issue is known and say a fix is coming.",
792
+ feature_request: "A feature request. Thank them for the feedback and promise to consider it."
793
+ },
794
+ localeNames: {
795
+ ko: "Korean",
796
+ en: "English",
797
+ ja: "Japanese",
798
+ "en-US": "English",
799
+ "zh-TW": "Traditional Chinese"
800
+ },
801
+ // Claude prompt
802
+ starsLabel: (stars, rating) => `Rating: ${stars} (${rating}/5)
803
+ `,
804
+ defaultDeveloper: "the dev team",
805
+ defaultApp: "the app",
806
+ system: (langName, toneGuide, developer) => `You reply to app store reviews on behalf of the app's developer. Reply in ${langName}, in 150 characters or fewer. ${toneGuide} Developer name: ${developer}`,
807
+ userPrompt: (app, stars, text, sentimentPrompt) => `App: ${app}
808
+ ${stars}Review: "${text}"
809
+
810
+ Guidance: ${sentimentPrompt}
811
+
812
+ Respond with JSON only: { "reply": "the reply text" }`,
813
+ parseFailed: "Failed to parse the AI response",
814
+ // cmdReview
815
+ title: "mimi-seed review \u2014 generate a review reply\n\n",
816
+ noApiKey: "ANTHROPIC_API_KEY is not set.\n",
817
+ noApiKeyHint: "Enable it: export ANTHROPIC_API_KEY=sk-ant-...\n",
818
+ textRequired: "--text <review body> is required.\n",
819
+ enterReview: "Paste the review (multi-line: finish with Ctrl+D):\n",
820
+ noInput: "Nothing entered. Exiting.\n",
821
+ meta: (stars, tone, language) => `Rating: ${stars} tone: ${tone} language: ${language}
822
+
823
+ `,
824
+ generating: "\u{1F916} Generating a reply with Claude AI...\n",
825
+ hdrReply: "\u2500\u2500\u2500 Suggested reply \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
826
+ draftWarning: "\n\u26A0 This reply is an AI-generated draft. Always review it before posting.\n\n",
827
+ postPrompt: "Post this reply to Play Store? [y/N]: ",
828
+ skipped: "Skipped.\n",
829
+ needAccount: "A Mimi Seed account is required. Run `mimi-seed init`.\n",
830
+ idsRequired: "--review-id and --package-name are required.\n",
831
+ packagePrompt: "Package name (e.g. com.example.app): ",
832
+ reviewIdPrompt: "Review ID: ",
833
+ notEnoughInfo: "Not enough information. Cancelled.\n",
834
+ posting: "Posting to Play Store...\n",
835
+ postFailed: (msg) => `Failed to post: ${msg}
836
+ `,
837
+ posted: "\u2713 Reply posted\n"
838
+ }
839
+ );
551
840
  function parseArgs3(argv) {
552
841
  const args = { tone: "friendly", language: "ko", apply: false, noInteractive: false };
553
842
  for (let i = 0; i < argv.length; i++) {
@@ -570,19 +859,6 @@ async function promptUser2(question) {
570
859
  rl.close();
571
860
  return answer.trim();
572
861
  }
573
- var TONE_GUIDES = {
574
- friendly: "\uCE5C\uADFC\uD558\uACE0 \uB530\uB73B\uD558\uAC8C. \uC774\uBAA8\uC9C0 1~2\uAC1C \uC0AC\uC6A9. \uAC10\uC0AC \uC778\uC0AC\uB85C \uC2DC\uC791.",
575
- professional: "\uC815\uC911\uD558\uACE0 \uACF5\uC2DD\uC801\uC73C\uB85C. \uBB38\uC81C\uB97C \uC778\uC815\uD558\uACE0 \uD574\uACB0\uCC45\uC744 \uC81C\uC2DC.",
576
- empathetic: "\uACF5\uAC10\uC744 \uBA3C\uC800 \uD45C\uD604. \uBD88\uD3B8\uC744 \uCDA9\uBD84\uD788 \uC778\uC815\uD55C \uD6C4 \uD574\uACB0 \uC758\uC9C0\uB97C \uBCF4\uC5EC\uC90C.",
577
- brief: "2~3\uBB38\uC7A5\uC73C\uB85C \uAC04\uACB0\uD558\uAC8C. \uD575\uC2EC \uC751\uB2F5\uB9CC."
578
- };
579
- var SENTIMENT_PROMPTS = {
580
- positive: "\uAE0D\uC815\uC801\uC778 \uB9AC\uBDF0. \uAC10\uC0AC \uC778\uC0AC\uC640 \uD568\uAED8 \uC55E\uC73C\uB85C\uB3C4 \uC88B\uC740 \uACBD\uD5D8\uC744 \uC81C\uACF5\uD558\uACA0\uB2E4\uB294 \uC758\uC9C0\uB97C \uD45C\uD604.",
581
- negative: "\uBD80\uC815\uC801\uC778 \uB9AC\uBDF0. \uBA3C\uC800 \uBD88\uD3B8\uC744 \uC0AC\uACFC\uD558\uACE0, \uBB38\uC81C \uD574\uACB0 \uC758\uC9C0\uB97C \uBCF4\uC5EC\uC918. \uAC00\uB2A5\uD558\uBA74 \uC9C0\uC6D0 \uCC44\uB110\uB85C \uC720\uB3C4.",
582
- neutral: "\uC911\uB9BD\uC801\uC778 \uB9AC\uBDF0. \uD53C\uB4DC\uBC31\uC5D0 \uAC10\uC0AC\uD558\uACE0 \uAC1C\uC120 \uB178\uB825\uC744 \uC57D\uC18D.",
583
- bug_report: "\uBC84\uADF8 \uB9AC\uD3EC\uD2B8. \uBB38\uC81C\uB97C \uD655\uC778\uD588\uC74C\uC744 \uC54C\uB9AC\uACE0, \uC218\uC815 \uC608\uC815\uC784\uC744 \uC804\uB2EC.",
584
- feature_request: "\uAE30\uB2A5 \uC694\uCCAD. \uD53C\uB4DC\uBC31\uC5D0 \uAC10\uC0AC\uD558\uACE0, \uAC80\uD1A0\uD558\uACA0\uB2E4\uACE0 \uC57D\uC18D."
585
- };
586
862
  function detectSentiment(text) {
587
863
  const lower = text.toLowerCase();
588
864
  if (["\uBC84\uADF8", "\uC624\uB958", "\uC548\uB428", "crash", "bug", "error", "broken"].some((w) => lower.includes(w))) return "bug_report";
@@ -593,103 +869,97 @@ function detectSentiment(text) {
593
869
  }
594
870
  async function generateReply(opts) {
595
871
  const client = new Anthropic2({ apiKey: process.env.ANTHROPIC_API_KEY });
872
+ const m = M4();
873
+ const toneGuides = m.toneGuides;
874
+ const sentimentPrompts = m.sentimentPrompts;
875
+ const localeNames = m.localeNames;
596
876
  const sentiment = detectSentiment(opts.text);
597
- const toneGuide = TONE_GUIDES[opts.tone] ?? TONE_GUIDES.friendly;
598
- const sentimentPrompt = SENTIMENT_PROMPTS[sentiment] ?? SENTIMENT_PROMPTS.neutral;
599
- const stars = opts.rating !== void 0 ? `\uBCC4\uC810: ${"\u2605".repeat(opts.rating)}${"\u2606".repeat(5 - opts.rating)} (${opts.rating}/5)
600
- ` : "";
601
- const LOCALE_NAMES = { ko: "\uD55C\uAD6D\uC5B4", en: "\uC601\uC5B4", ja: "\uC77C\uBCF8\uC5B4", "en-US": "\uC601\uC5B4", "zh-TW": "\uBC88\uCCB4\uC911\uAD6D\uC5B4" };
602
- const langName = LOCALE_NAMES[opts.language] ?? opts.language;
877
+ const toneGuide = toneGuides[opts.tone] ?? toneGuides.friendly;
878
+ const sentimentPrompt = sentimentPrompts[sentiment] ?? sentimentPrompts.neutral;
879
+ const stars = opts.rating !== void 0 ? m.starsLabel(`${"\u2605".repeat(opts.rating)}${"\u2606".repeat(5 - opts.rating)}`, opts.rating) : "";
880
+ const langName = localeNames[opts.language] ?? opts.language;
603
881
  const response = await client.messages.create({
604
882
  model: "claude-haiku-4-5-20251001",
605
883
  max_tokens: 500,
606
- system: `\uC571 \uAC1C\uBC1C\uC790\uB97C \uB300\uC2E0\uD574 \uC2A4\uD1A0\uC5B4 \uB9AC\uBDF0\uC5D0 \uB2F5\uBCC0\uD558\uB294 \uC804\uBB38\uAC00. ${langName}\uB85C 150\uC790 \uC774\uB0B4\uB85C \uB2F5\uBCC0. ${toneGuide} \uAC1C\uBC1C\uC790 \uC774\uB984: ${opts.developerName ?? "\uAC1C\uBC1C\uD300"}`,
884
+ system: m.system(langName, toneGuide, opts.developerName ?? m.defaultDeveloper),
607
885
  messages: [{
608
886
  role: "user",
609
- content: `\uC571: ${opts.appName ?? "\uC571"}
610
- ${stars}\uB9AC\uBDF0: "${opts.text}"
611
-
612
- \uC9C0\uCE68: ${sentimentPrompt}
613
-
614
- JSON\uC73C\uB85C\uB9CC \uC751\uB2F5: { "reply": "\uB2F5\uBCC0 \uD14D\uC2A4\uD2B8" }`
887
+ content: m.userPrompt(opts.appName ?? m.defaultApp, stars, opts.text, sentimentPrompt)
615
888
  }]
616
889
  });
617
890
  const raw = response.content[0].type === "text" ? response.content[0].text : "";
618
891
  const match = raw.match(/\{[\s\S]*\}/);
619
- if (!match) throw new Error("AI \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328");
892
+ if (!match) throw new Error(m.parseFailed);
620
893
  const parsed = JSON.parse(match[0]);
621
894
  return parsed.reply;
622
895
  }
623
896
  async function cmdReview(argv) {
624
897
  const args = parseArgs3(argv);
625
- process.stdout.write(kleur4.bold("mimi-seed review \u2014 \uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\n\n"));
898
+ process.stdout.write(kleur4.bold(M4().title));
626
899
  if (!process.env.ANTHROPIC_API_KEY) {
627
- process.stdout.write(kleur4.red("ANTHROPIC_API_KEY\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
628
- process.stdout.write(kleur4.dim("\uD65C\uC131\uD654: export ANTHROPIC_API_KEY=sk-ant-...\n"));
900
+ process.stdout.write(kleur4.red(M4().noApiKey));
901
+ process.stdout.write(kleur4.dim(M4().noApiKeyHint));
629
902
  process.exit(1);
630
903
  }
631
904
  let reviewText = args.text;
632
905
  if (!reviewText) {
633
906
  if (args.noInteractive) {
634
- process.stdout.write(kleur4.red("--text <\uB9AC\uBDF0 \uB0B4\uC6A9> \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n"));
907
+ process.stdout.write(kleur4.red(M4().textRequired));
635
908
  process.exit(1);
636
909
  }
637
- process.stdout.write(kleur4.dim("\uB9AC\uBDF0 \uB0B4\uC6A9\uC744 \uC785\uB825\uD558\uC138\uC694 (\uC5EC\uB7EC \uC904: Ctrl+D\uB85C \uC644\uB8CC):\n"));
910
+ process.stdout.write(kleur4.dim(M4().enterReview));
638
911
  reviewText = await promptUser2("> ");
639
912
  if (!reviewText) {
640
- process.stdout.write(kleur4.yellow("\uC785\uB825 \uC5C6\uC74C. \uC885\uB8CC.\n"));
913
+ process.stdout.write(kleur4.yellow(M4().noInput));
641
914
  process.exit(0);
642
915
  }
643
916
  }
644
917
  if (args.rating !== void 0) {
645
918
  const stars = "\u2605".repeat(args.rating) + "\u2606".repeat(5 - args.rating);
646
- process.stdout.write(kleur4.dim(`\uBCC4\uC810: ${stars} tone: ${args.tone} \uC5B8\uC5B4: ${args.language}
647
-
648
- `));
919
+ process.stdout.write(kleur4.dim(M4().meta(stars, args.tone, args.language)));
649
920
  }
650
- process.stdout.write("\u{1F916} Claude AI\uB85C \uB2F5\uBCC0 \uC0DD\uC131 \uC911...\n");
921
+ process.stdout.write(M4().generating);
651
922
  const reply = await generateReply({ ...args, text: reviewText });
652
- process.stdout.write("\n" + kleur4.bold("\u2500\u2500\u2500 \uC81C\uC548 \uB2F5\uBCC0 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
923
+ process.stdout.write("\n" + kleur4.bold(M4().hdrReply));
653
924
  process.stdout.write(reply + "\n");
654
- process.stdout.write(kleur4.dim("\n\u26A0 \uC774 \uB2F5\uBCC0\uC740 AI\uAC00 \uC0DD\uC131\uD55C \uCD08\uC548\uC785\uB2C8\uB2E4. \uAC8C\uC2DC \uC804 \uBC18\uB4DC\uC2DC \uAC80\uD1A0\uD558\uC138\uC694.\n\n"));
925
+ process.stdout.write(kleur4.dim(M4().draftWarning));
655
926
  const shouldPrompt = !args.apply && !args.noInteractive && process.stdout.isTTY;
656
927
  if (!args.apply && !shouldPrompt) return;
657
928
  let doApply = args.apply;
658
929
  if (shouldPrompt) {
659
- const ans = await promptUser2("\uC774 \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC\uD560\uAE4C\uC694? [y/N]: ");
930
+ const ans = await promptUser2(M4().postPrompt);
660
931
  doApply = ans.toLowerCase() === "y";
661
932
  }
662
933
  if (!doApply) {
663
- process.stdout.write(kleur4.dim("\uAC74\uB108\uB700.\n"));
934
+ process.stdout.write(kleur4.dim(M4().skipped));
664
935
  return;
665
936
  }
666
937
  const cfg = await getEffectiveConfig();
667
938
  if (!cfg) {
668
- process.stdout.write(kleur4.yellow("Mimi Seed \uACC4\uC815 \uC5F0\uACB0 \uD544\uC694. `mimi-seed init` \uC2E4\uD589.\n"));
939
+ process.stdout.write(kleur4.yellow(M4().needAccount));
669
940
  return;
670
941
  }
671
942
  let reviewId = args.reviewId;
672
943
  let packageName = args.packageName;
673
944
  if (!reviewId || !packageName) {
674
945
  if (args.noInteractive) {
675
- process.stdout.write(kleur4.red("--review-id \uC640 --package-name \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n"));
946
+ process.stdout.write(kleur4.red(M4().idsRequired));
676
947
  process.exit(1);
677
948
  }
678
- if (!packageName) packageName = await promptUser2("\uD328\uD0A4\uC9C0\uBA85 (\uC608: com.example.app): ");
679
- if (!reviewId) reviewId = await promptUser2("\uB9AC\uBDF0 ID: ");
949
+ if (!packageName) packageName = await promptUser2(M4().packagePrompt);
950
+ if (!reviewId) reviewId = await promptUser2(M4().reviewIdPrompt);
680
951
  }
681
952
  if (!reviewId || !packageName) {
682
- process.stdout.write(kleur4.yellow("\uC815\uBCF4 \uBD80\uC871. \uCDE8\uC18C.\n"));
953
+ process.stdout.write(kleur4.yellow(M4().notEnoughInfo));
683
954
  return;
684
955
  }
685
- process.stdout.write("Play Store\uC5D0 \uAC8C\uC2DC \uC911...\n");
956
+ process.stdout.write(M4().posting);
686
957
  const r = await mcpCall(cfg.endpoint, cfg.token, "playstore_reply_review", {
687
958
  package_name: packageName,
688
959
  review_id: reviewId,
689
960
  reply_text: reply
690
961
  });
691
- process.stdout.write(r.isError ? kleur4.red(`\uAC8C\uC2DC \uC2E4\uD328: ${r.text}
692
- `) : kleur4.green("\u2713 \uB2F5\uBCC0 \uAC8C\uC2DC \uC644\uB8CC\n"));
962
+ process.stdout.write(r.isError ? kleur4.red(M4().postFailed(r.text)) : kleur4.green(M4().posted));
693
963
  }
694
964
 
695
965
  // src/auth.ts
@@ -697,8 +967,9 @@ import kleur5 from "kleur";
697
967
  function log(msg) {
698
968
  process.stdout.write(msg + "\n");
699
969
  }
700
- function printAuthHelp() {
701
- log(`${kleur5.bold("mimi-seed auth")} \u2014 \uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC778\uC99D/\uAD00\uB9AC
970
+ var M5 = catalog(
971
+ {
972
+ help: `${kleur5.bold("mimi-seed auth")} \u2014 \uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC778\uC99D/\uAD00\uB9AC
702
973
 
703
974
  ${kleur5.bold("Google OAuth (Firebase / AdMob / Play):")}
704
975
  ${kleur5.cyan("mimi-seed auth login")} \uBE0C\uB77C\uC6B0\uC800\uB85C \uB85C\uADF8\uC778 (\uC774\uBBF8 \uC788\uC73C\uBA74 \uC790\uB3D9 refresh \uC2DC\uB3C4)
@@ -729,18 +1000,58 @@ ${kleur5.bold("login \uC635\uC158:")}
729
1000
  --timeout <\uCD08> \uCF5C\uBC31 \uB300\uAE30 \uC2DC\uAC04 (\uAE30\uBCF8 600)
730
1001
  --force \uAE30\uC874 \uD1A0\uD070 \uBB34\uC2DC\uD558\uACE0 \uAC15\uC81C \uC7AC\uB85C\uADF8\uC778
731
1002
 
732
- ${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-*-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`);
1003
+ ${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-*-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`,
1004
+ connectAll: "\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup"
1005
+ },
1006
+ {
1007
+ help: `${kleur5.bold("mimi-seed auth")} \u2014 local credential setup
1008
+
1009
+ ${kleur5.bold("Google OAuth (Firebase / AdMob / Play):")}
1010
+ ${kleur5.cyan("mimi-seed auth login")} sign in via browser (refreshes an existing token if it can)
1011
+ ${kleur5.cyan("mimi-seed auth status")} current OAuth token status
1012
+ ${kleur5.cyan("mimi-seed auth refresh")} refresh only, using refresh_token (no browser)
1013
+ ${kleur5.cyan("mimi-seed auth logout")} delete the OAuth token
1014
+
1015
+ ${kleur5.bold("Per-platform credentials:")}
1016
+ ${kleur5.cyan("mimi-seed auth appstore")} App Store Connect API key (appstore.json)
1017
+ ${kleur5.cyan("mimi-seed auth playstore")} register a Play service-account JSON
1018
+ ${kleur5.cyan("mimi-seed auth bigquery")} BigQuery service account (Crashlytics export, etc.)
1019
+
1020
+ ${kleur5.bold("Build / marketing:")}
1021
+ ${kleur5.cyan("mimi-seed auth jenkins")} Jenkins connection (jenkins.json)
1022
+ ${kleur5.cyan("mimi-seed auth ci")} GitHub Actions / GitLab CI (ci.json)
1023
+ ${kleur5.cyan("mimi-seed auth googleads")} Google Ads (google-ads.json)
1024
+ ${kleur5.cyan("mimi-seed auth facebook")} Facebook Page
1025
+ ${kleur5.cyan("mimi-seed auth instagram")} Instagram
1026
+
1027
+ ${kleur5.bold("Everything at a glance:")}
1028
+ ${kleur5.cyan("mimi-seed auth status --all")} which credentials you have
1029
+
1030
+ ${kleur5.bold("Connect them all in one pass:")}
1031
+ ${kleur5.cyan("mimi-seed setup")} guided, one credential at a time (recommended)
1032
+
1033
+ ${kleur5.bold("login options:")}
1034
+ --no-browser don't open the URL automatically (paste it yourself)
1035
+ --timeout <sec> how long to wait for the callback (default 600)
1036
+ --force ignore the existing token and sign in again
1037
+
1038
+ ${kleur5.dim(`Under the hood this calls the mimi-seed-*-auth CLIs from ${MCP_PKG}.`)}`,
1039
+ connectAll: "\n Connect everything at once: mimi-seed setup"
1040
+ }
1041
+ );
1042
+ function printAuthHelp() {
1043
+ log(M5().help);
733
1044
  }
734
1045
  function printAllCredStatus() {
735
- log(kleur5.bold("\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC0C1\uD0DC ") + kleur5.dim("(~/.mimi-seed)"));
1046
+ log(kleur5.bold(t().auth.statusTitle + " ") + kleur5.dim("(~/.mimi-seed)"));
736
1047
  const detected = detectAll();
737
1048
  for (const spec of CREDENTIALS) {
738
1049
  const d = detected.get(spec.id);
739
1050
  const mark = d.present ? kleur5.green("\u2713") : isSatisfied(spec, detected) ? kleur5.yellow("~") : spec.requirement === "optional" ? kleur5.dim("\xB7") : kleur5.red("\u2717");
740
1051
  const tail = d.present ? kleur5.dim(d.detail ?? "") : kleur5.dim(`\u2192 ${spec.fix}`);
741
- log(` ${mark} ${spec.label.padEnd(24)} ${tail}`);
1052
+ log(` ${mark} ${credLabel(spec).padEnd(24)} ${tail}`);
742
1053
  }
743
- log(kleur5.dim("\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup"));
1054
+ log(kleur5.dim(M5().connectAll));
744
1055
  }
745
1056
  async function cmdAuth(args) {
746
1057
  const sub = args[0];
@@ -761,7 +1072,7 @@ async function cmdAuth(args) {
761
1072
  if (sub === "facebook") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["facebook"]));
762
1073
  if (sub === "instagram") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["instagram"]));
763
1074
  if (sub === "ci") {
764
- const { cmdSetup: cmdSetup2 } = await import("./setup-ENJIMGJF.js");
1075
+ const { cmdSetup: cmdSetup2 } = await import("./setup-NQHLA3AJ.js");
765
1076
  await cmdSetup2(["--only", "github,gitlab", "--reconnect", "github,gitlab"]);
766
1077
  return;
767
1078
  }
@@ -781,7 +1092,7 @@ async function cmdAuth(args) {
781
1092
  mcpArgs = ["--logout", ...rest];
782
1093
  break;
783
1094
  default:
784
- log(kleur5.red(`\uC54C \uC218 \uC5C6\uB294 auth \uC11C\uBE0C\uBA85\uB839: ${sub}`));
1095
+ log(kleur5.red(t().auth.unknownSub(sub ?? "")));
785
1096
  printAuthHelp();
786
1097
  process.exit(1);
787
1098
  }
@@ -791,6 +1102,33 @@ function exitWith(code) {
791
1102
  if (code !== 0) process.exit(code);
792
1103
  }
793
1104
 
1105
+ // src/lang.ts
1106
+ import kleur6 from "kleur";
1107
+ function log2(msg) {
1108
+ process.stdout.write(msg + "\n");
1109
+ }
1110
+ async function cmdLang(args) {
1111
+ const arg = args[0]?.toLowerCase();
1112
+ if (!arg) {
1113
+ log2(t().lang.current(resolveLang()));
1114
+ if (process.env.MIMI_SEED_LANG) {
1115
+ log2(kleur6.dim(" (MIMI_SEED_LANG)"));
1116
+ }
1117
+ return;
1118
+ }
1119
+ if (arg === "--help" || arg === "-h") {
1120
+ log2(t().lang.usage);
1121
+ return;
1122
+ }
1123
+ if (!isLang(arg)) {
1124
+ log2(kleur6.red(t().lang.invalid(arg)));
1125
+ process.exit(1);
1126
+ }
1127
+ writeSettings({ lang: arg });
1128
+ process.env.MIMI_SEED_LANG = arg;
1129
+ log2(kleur6.green(t().lang.saved(arg)));
1130
+ }
1131
+
794
1132
  // src/cloud.ts
795
1133
  import { spawn } from "child_process";
796
1134
  var MCP_PKG2 = "@yoonion/mimi-seed-mcp";
@@ -801,9 +1139,7 @@ async function runMcpBin2(bin, extraArgs) {
801
1139
  shell: true
802
1140
  });
803
1141
  child.on("error", (e) => {
804
- process.stderr.write(`
805
- \u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
806
- `);
1142
+ process.stderr.write(t().auth.npxFailed("npx", e.message));
807
1143
  resolve(1);
808
1144
  });
809
1145
  child.on("exit", (code) => resolve(code ?? 0));
@@ -827,10 +1163,46 @@ import { execSync as execSync2 } from "child_process";
827
1163
  import fs2 from "fs";
828
1164
  import os from "os";
829
1165
  import path2 from "path";
830
- import kleur6 from "kleur";
831
- function log2(msg) {
1166
+ import kleur7 from "kleur";
1167
+ function log3(msg) {
832
1168
  process.stdout.write(msg + "\n");
833
1169
  }
1170
+ var M6 = catalog(
1171
+ {
1172
+ killedPid: (pid) => ` PID ${pid} \uC885\uB8CC`,
1173
+ title: (server) => `MCP \uC11C\uBC84 \uC7AC\uC2DC\uC791: ${server}`,
1174
+ none: "(\uC5C6\uC74C)",
1175
+ notFound: (server) => `'${server}' \uC11C\uBC84\uB97C ~/.claude/.claude.json\uC5D0\uC11C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.`,
1176
+ registered: (list) => ` \uB4F1\uB85D\uB41C \uC11C\uBC84: ${list}`,
1177
+ httpServer: (server) => `'${server}'\uB294 HTTP/SSE \uC11C\uBC84\uC785\uB2C8\uB2E4. \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`,
1178
+ httpHint: " Claude Code\uC5D0\uC11C /mcp \uB97C \uC2E4\uD589\uD574 \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694.",
1179
+ noMarker: "\uD504\uB85C\uC138\uC2A4 \uC2DD\uBCC4\uC790(\uC2A4\uD06C\uB9BD\uD2B8 \uACBD\uB85C)\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
1180
+ configLine: (cfg) => ` \uC124\uC815: ${cfg}`,
1181
+ markerLine: (marker) => ` \uC2DD\uBCC4\uC790: ${marker}`,
1182
+ noProcess: "\u26A0 \uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
1183
+ noProcessHint: " \uC774\uBBF8 \uC885\uB8CC\uB410\uAC70\uB098, Claude Code\uAC00 \uC544\uC9C1 \uC11C\uBC84\uB97C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC740 \uC0C1\uD0DC\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
1184
+ killed: (server, n) => `\u2713 ${server} \uC885\uB8CC\uB428 (${n}\uAC1C \uD504\uB85C\uC138\uC2A4)`,
1185
+ killedHint: " Claude Code\uAC00 \uB2E4\uC74C \uB3C4\uAD6C \uD638\uCD9C \uC2DC \uC790\uB3D9\uC73C\uB85C \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4.",
1186
+ verify: " \uC5F0\uACB0 \uD655\uC778: Claude Code\uC5D0\uC11C /mcp \uC2E4\uD589"
1187
+ },
1188
+ {
1189
+ killedPid: (pid) => ` PID ${pid} killed`,
1190
+ title: (server) => `Restarting MCP server: ${server}`,
1191
+ none: "(none)",
1192
+ registered: (list) => ` Registered servers: ${list}`,
1193
+ notFound: (server) => `Could not find the '${server}' server in ~/.claude/.claude.json.`,
1194
+ httpServer: (server) => `'${server}' is an HTTP/SSE server. It does not need a process restart.`,
1195
+ httpHint: " Run /mcp in Claude Code to check the connection.",
1196
+ noMarker: "Could not find a process marker (script path).",
1197
+ configLine: (cfg) => ` Config: ${cfg}`,
1198
+ markerLine: (marker) => ` Marker: ${marker}`,
1199
+ noProcess: "\u26A0 No running process found.",
1200
+ noProcessHint: " It may have already exited, or Claude Code may not have started the server yet.",
1201
+ killed: (server, n) => `\u2713 ${server} killed (${n} process(es))`,
1202
+ killedHint: " Claude Code will reconnect automatically on the next tool call.",
1203
+ verify: " Verify the connection: run /mcp in Claude Code"
1204
+ }
1205
+ );
834
1206
  function readClaudeJson() {
835
1207
  const p = path2.join(os.homedir(), ".claude.json");
836
1208
  try {
@@ -867,7 +1239,7 @@ function killByMarker(marker) {
867
1239
  for (const pid of pids) {
868
1240
  try {
869
1241
  execSync2(`taskkill /F /PID ${pid}`, { stdio: "pipe" });
870
- log2(kleur6.dim(` PID ${pid} \uC885\uB8CC`));
1242
+ log3(kleur7.dim(M6().killedPid(pid)));
871
1243
  killed++;
872
1244
  } catch {
873
1245
  }
@@ -884,48 +1256,62 @@ function killByMarker(marker) {
884
1256
  }
885
1257
  async function cmdRestart(args) {
886
1258
  const serverName = args[0] ?? "mimi-seed";
887
- log2(kleur6.bold(`MCP \uC11C\uBC84 \uC7AC\uC2DC\uC791: ${serverName}`));
888
- log2("");
1259
+ log3(kleur7.bold(M6().title(serverName)));
1260
+ log3("");
889
1261
  const d = readClaudeJson();
890
1262
  const servers = d.mcpServers;
891
1263
  const cfg = servers?.[serverName];
892
1264
  if (!cfg) {
893
- const available = servers ? Object.keys(servers).join(", ") : "(\uC5C6\uC74C)";
894
- log2(kleur6.red(`'${serverName}' \uC11C\uBC84\uB97C ~/.claude/.claude.json\uC5D0\uC11C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.`));
895
- log2(kleur6.dim(` \uB4F1\uB85D\uB41C \uC11C\uBC84: ${available}`));
1265
+ const available = servers ? Object.keys(servers).join(", ") : M6().none;
1266
+ log3(kleur7.red(M6().notFound(serverName)));
1267
+ log3(kleur7.dim(M6().registered(available)));
896
1268
  process.exit(1);
897
1269
  }
898
1270
  if (cfg.type === "http" || cfg.type === "sse") {
899
- log2(kleur6.yellow(`'${serverName}'\uB294 HTTP/SSE \uC11C\uBC84\uC785\uB2C8\uB2E4. \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
900
- log2(kleur6.dim(" Claude Code\uC5D0\uC11C /mcp \uB97C \uC2E4\uD589\uD574 \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
1271
+ log3(kleur7.yellow(M6().httpServer(serverName)));
1272
+ log3(kleur7.dim(M6().httpHint));
901
1273
  return;
902
1274
  }
903
1275
  const marker = findProcessMarker(cfg);
904
1276
  if (!marker) {
905
- log2(kleur6.yellow("\uD504\uB85C\uC138\uC2A4 \uC2DD\uBCC4\uC790(\uC2A4\uD06C\uB9BD\uD2B8 \uACBD\uB85C)\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."));
906
- log2(kleur6.dim(` \uC124\uC815: ${JSON.stringify(cfg)}`));
1277
+ log3(kleur7.yellow(M6().noMarker));
1278
+ log3(kleur7.dim(M6().configLine(JSON.stringify(cfg))));
907
1279
  process.exit(1);
908
1280
  }
909
- log2(kleur6.dim(` \uC2DD\uBCC4\uC790: ${marker}`));
1281
+ log3(kleur7.dim(M6().markerLine(marker)));
910
1282
  const { killed } = killByMarker(marker);
911
1283
  if (killed === 0) {
912
- log2(kleur6.yellow("\u26A0 \uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."));
913
- log2(kleur6.dim(" \uC774\uBBF8 \uC885\uB8CC\uB410\uAC70\uB098, Claude Code\uAC00 \uC544\uC9C1 \uC11C\uBC84\uB97C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC740 \uC0C1\uD0DC\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
1284
+ log3(kleur7.yellow(M6().noProcess));
1285
+ log3(kleur7.dim(M6().noProcessHint));
914
1286
  } else {
915
- log2("");
916
- log2(kleur6.green(`\u2713 ${serverName} \uC885\uB8CC\uB428 (${killed}\uAC1C \uD504\uB85C\uC138\uC2A4)`));
917
- log2(kleur6.dim(" Claude Code\uAC00 \uB2E4\uC74C \uB3C4\uAD6C \uD638\uCD9C \uC2DC \uC790\uB3D9\uC73C\uB85C \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4."));
1287
+ log3("");
1288
+ log3(kleur7.green(M6().killed(serverName, killed)));
1289
+ log3(kleur7.dim(M6().killedHint));
918
1290
  }
919
- log2("");
920
- log2(kleur6.cyan(" \uC5F0\uACB0 \uD655\uC778: Claude Code\uC5D0\uC11C /mcp \uC2E4\uD589"));
1291
+ log3("");
1292
+ log3(kleur7.cyan(M6().verify));
921
1293
  }
922
1294
 
923
1295
  // src/mcp-config.ts
924
1296
  import fs3 from "fs/promises";
925
1297
  import path3 from "path";
926
1298
  import os2 from "os";
927
- import kleur7 from "kleur";
1299
+ import kleur8 from "kleur";
928
1300
  var SERVER_NAME = "mimi-seed";
1301
+ var M7 = catalog(
1302
+ {
1303
+ claudeTitle: "Claude Code MCP \uB4F1\uB85D \u2014 \uC544\uB798 \uD55C \uC904\uC744 \uADF8\uB300\uB85C \uC2E4\uD589\uD558\uC138\uC694:",
1304
+ tokenWarning: (loc) => ` \u26A0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3EC\uD568\uB41C \uBA85\uB839\uC785\uB2C8\uB2E4 (\uC178 \uD788\uC2A4\uD1A0\uB9AC\uC5D0 \uB0A8\uC74C). \uD1A0\uD070\uC740 ${loc} \uC5D0\uB3C4 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.`,
1305
+ codexTitle: "Codex MCP \uB4F1\uB85D:",
1306
+ codexManual: " # \uB610\uB294 \uC218\uB3D9\uC73C\uB85C ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed] \uCD94\uAC00"
1307
+ },
1308
+ {
1309
+ claudeTitle: "Register the MCP server with Claude Code \u2014 run this single line as-is:",
1310
+ tokenWarning: (loc) => ` \u26A0 This command contains your real token (it lands in your shell history). The token is also stored in ${loc}.`,
1311
+ codexTitle: "Register with Codex:",
1312
+ codexManual: " # or add [mcp_servers.mimi-seed] to ~/.codex/config.toml by hand"
1313
+ }
1314
+ );
929
1315
  function tomlString(value) {
930
1316
  return JSON.stringify(value);
931
1317
  }
@@ -956,13 +1342,13 @@ function claudeMcpAddCommand(cfg) {
956
1342
  function printMcpSetup(cfg) {
957
1343
  process.stdout.write(
958
1344
  [
959
- kleur7.bold("Claude Code MCP \uB4F1\uB85D \u2014 \uC544\uB798 \uD55C \uC904\uC744 \uADF8\uB300\uB85C \uC2E4\uD589\uD558\uC138\uC694:"),
960
- kleur7.cyan(` ${claudeMcpAddCommand(cfg)}`),
961
- kleur7.dim(` \u26A0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3EC\uD568\uB41C \uBA85\uB839\uC785\uB2C8\uB2E4 (\uC178 \uD788\uC2A4\uD1A0\uB9AC\uC5D0 \uB0A8\uC74C). \uD1A0\uD070\uC740 ${CONFIG_LOCATION_HINT} \uC5D0\uB3C4 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.`),
1345
+ kleur8.bold(M7().claudeTitle),
1346
+ kleur8.cyan(` ${claudeMcpAddCommand(cfg)}`),
1347
+ kleur8.dim(M7().tokenWarning(CONFIG_LOCATION_HINT)),
962
1348
  "",
963
- kleur7.dim("Codex MCP \uB4F1\uB85D:"),
964
- kleur7.dim(" mimi-seed mcp codex --write"),
965
- kleur7.dim(" # \uB610\uB294 \uC218\uB3D9\uC73C\uB85C ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed] \uCD94\uAC00")
1349
+ kleur8.dim(M7().codexTitle),
1350
+ kleur8.dim(" mimi-seed mcp codex --write"),
1351
+ kleur8.dim(M7().codexManual)
966
1352
  ].join("\n") + "\n"
967
1353
  );
968
1354
  }
@@ -1027,62 +1413,391 @@ async function ensureReleaseManifest(cwd) {
1027
1413
  // src/index.ts
1028
1414
  var DEFAULT_WEB_BASE = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
1029
1415
  var DEFAULT_MCP_ENDPOINT = `${DEFAULT_WEB_BASE}/api/mcp`;
1030
- function log3(msg) {
1416
+ var M8 = catalog(
1417
+ {
1418
+ noProjectSignal: "\u26A0 package.json / app.json / android / ios \uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD569\uB2C8\uB2E4.",
1419
+ detecting: "\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...",
1420
+ noAppDetected: "\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5.",
1421
+ unnamed: "(\uC774\uB984 \uBBF8\uC0C1)",
1422
+ ciMode: "CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9",
1423
+ syncFailed: (text) => "\uB4F1\uB85D \uC2E4\uD328: " + text,
1424
+ manifest: (created) => ` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`,
1425
+ doneCi: "\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)",
1426
+ waitingLogin: "\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...",
1427
+ connectFailed: (msg) => "\uC5F0\uACB0 \uC2E4\uD328: " + msg,
1428
+ tokenReceived: "\u2713 \uD1A0\uD070 \uC218\uC2E0",
1429
+ saved: (location) => ` \uC800\uC7A5\uB428: ${location}`,
1430
+ registering: "\u{1F504} \uC571 \uB4F1\uB85D \uC911...",
1431
+ claudeAgent: " Claude \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md",
1432
+ codexAgent: " Codex \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: AGENTS.md",
1433
+ ready: "\u2713 \uC900\uBE44 \uC644\uB8CC.",
1434
+ askLike: "Claude Code \uB610\uB294 Codex\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:",
1435
+ ask1: ' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"',
1436
+ ask2: ' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"',
1437
+ ask3: ' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"',
1438
+ dashboard: (url) => `\uB300\uC2DC\uBCF4\uB4DC: ${url}`,
1439
+ localTitle: "\u2500\u2500 \uB85C\uCEEC MCP \uCD94\uAC00 \uC124\uC815 (--local) \u2500\u2500",
1440
+ localIntro: "\uC6D0\uACA9 MCP(PAT, \uC77D\uAE30\xB7\uC9C4\uB2E8)\uB294 \uC704\uC5D0\uC11C \uB05D. \uB85C\uCEEC MCP\uB294 Google OAuth\uB85C \uC2A4\uD1A0\uC5B4 \uC4F0\uAE30 \uB3C4\uAD6C \uC804\uCCB4\uB97C \uC9C1\uC811 \uC2E4\uD589\uD569\uB2C8\uB2E4 (Node 20+).",
1441
+ localStep1: "1) Google \uB85C\uADF8\uC778 (Firebase / AdMob / Play / Ads):",
1442
+ localStep2: "2) \uB85C\uCEEC MCP \uC11C\uBC84 \uB4F1\uB85D (\uC6D0\uACA9 'mimi-seed' \uC640 \uBCC4\uAC1C):",
1443
+ localCodexHint: ' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"]',
1444
+ localStep3: "3) \uB098\uBA38\uC9C0 \uACC4\uC815 \uC5F0\uACB0 (App Store / Play / Jenkins / CI / \uC18C\uC15C \u2026):",
1445
+ localSetupHint: " \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 \uD1A0\uD070 \uBC1C\uAE09 \uBC29\uBC95\uC744 \uC54C\uB824\uC90D\uB2C8\uB2E4.",
1446
+ notConnected: "\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589.",
1447
+ statusTitle: "Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC",
1448
+ statusToken: (prefix, date) => ` \uD1A0\uD070: ${prefix}\u2026 (${date})`,
1449
+ statusEndpoint: (endpoint) => ` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${endpoint}`,
1450
+ appList: "\u{1F4CB} \uC571 \uBAA9\uB85D:",
1451
+ listFailed: (text) => "\uC870\uD68C \uC2E4\uD328: " + text,
1452
+ logoutDone: "\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC.",
1453
+ logoutRevoke: "\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens",
1454
+ codexWritten: "\u2713 Codex MCP \uC124\uC815 \uC644\uB8CC",
1455
+ codexWriteWarn: " \u26A0 config.toml\uC5D0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3C9\uBB38\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4 (\uD30C\uC77C \uAD8C\uD55C \uD655\uC778 \uAD8C\uC7A5).",
1456
+ codexVerify: "Codex\uB97C \uC0C8\uB85C \uC5F4\uACE0 `/mcp` \uB610\uB294 `codex mcp list`\uB85C \uD655\uC778\uD558\uC138\uC694.",
1457
+ codexTitle: "Codex MCP \uB4F1\uB85D",
1458
+ codexAuto: "\uC790\uB3D9 \uB4F1\uB85D:",
1459
+ codexManual: "\uC218\uB3D9 \uB4F1\uB85D \uC608\uC2DC (~/.codex/config.toml) \u2014 \uC2E4\uC81C \uD1A0\uD070 \uD3EC\uD568:",
1460
+ claudeTitle: "Claude Code MCP \uB4F1\uB85D \u2014 \uC544\uB798 \uD55C \uC904\uC744 \uADF8\uB300\uB85C \uC2E4\uD589\uD558\uC138\uC694:",
1461
+ claudeWarn: " \u26A0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3EC\uD568\uB41C \uBA85\uB839\uC785\uB2C8\uB2E4 (\uC178 \uD788\uC2A4\uD1A0\uB9AC\uC5D0 \uB0A8\uC74C). \uD1A0\uD070\uC740 ~/.mimi-seed/config.json \uC5D0\uB3C4 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
1462
+ // 명령별 상세 사용법 (SSOT). `mimi-seed <command> --help` 와 overview 가 공유.
1463
+ usage: {
1464
+ init: `${kleur9.bold("mimi-seed init")} \u2014 \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1465
+
1466
+ \uC571 \uC790\uB3D9 \uAC10\uC9C0(Expo/Gradle/Info.plist/pbxproj) \u2192 \uBE0C\uB77C\uC6B0\uC800 PAT \uBC1C\uAE09(\uC6D0\uACA9 MCP) \u2192 \uC571 \uB4F1\uB85D
1467
+ + .claude/mimi-seed.md, AGENTS.md, docs/releases.json \uC0DD\uC131/\uD655\uC778.
1468
+
1469
+ \uC635\uC158:
1470
+ --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`,
1471
+ setup: `${kleur9.bold("mimi-seed setup")} \u2014 \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
1472
+
1473
+ \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.
1474
+ \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.
1475
+ \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.
1476
+
1477
+ \uC635\uC158:
1478
+ --only <ids> \uC9C0\uC815\uD55C \uC790\uACA9\uC99D\uBA85\uB9CC (\uC27C\uD45C \uAD6C\uBD84: oauth,appstore,jenkins \u2026)
1479
+ --reconnect <ids> \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uB3C4 \uB2E4\uC2DC \uC124\uC815
1480
+ --platform android,ios \uD50C\uB7AB\uD3FC \uAC15\uC81C \uC9C0\uC815 (\uAE30\uBCF8: \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uB3D9 \uAC10\uC9C0)
1481
+ --yes, -y \uC544\uBB34\uAC83\uB3C4 \uBB3B\uC9C0 \uC54A\uACE0 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825
1482
+ --non-interactive \uC704\uC640 \uB3D9\uC77C (CI \uC6A9)
1483
+ --fail-on-missing \uD544\uC218 \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC73C\uBA74 exit 1 (CI \uAC8C\uC774\uD2B8)
1484
+
1485
+ ${kleur9.dim("\uBE44TTY / CI \uD658\uACBD\uC5D0\uC11C\uB294 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825\uD55C\uB2E4.")}`,
1486
+ lang: `${kleur9.bold("mimi-seed lang")} \u2014 CLI \uCD9C\uB825 \uC5B8\uC5B4 (\uD55C\uAD6D\uC5B4 / English)
1487
+
1488
+ mimi-seed lang \uD604\uC7AC \uC5B8\uC5B4 \uD45C\uC2DC
1489
+ mimi-seed lang ko \uD55C\uAD6D\uC5B4 (\uAE30\uBCF8)
1490
+ mimi-seed lang en English
1491
+
1492
+ ${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.")}
1493
+ ${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.")}`,
1494
+ status: `${kleur9.bold("mimi-seed status")} \u2014 \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D. \uC635\uC158 \uC5C6\uC74C.`,
1495
+ 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.`,
1496
+ logout: `${kleur9.bold("mimi-seed logout")} \u2014 \uB85C\uCEEC \uC124\uC815(config.json) \uC0AD\uC81C. \uC635\uC158 \uC5C6\uC74C.`,
1497
+ restart: `${kleur9.bold("mimi-seed restart")} \u2014 MCP \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791 (\uAE30\uBCF8: mimi-seed)
1498
+
1499
+ mimi-seed restart [server-name]`,
1500
+ notes: `${kleur9.bold("mimi-seed notes")} \u2014 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
1501
+
1502
+ \uC635\uC158:
1503
+ --from <ref> \uC2DC\uC791 \uCEE4\uBC0B/\uD0DC\uADF8 (\uAE30\uBCF8: \uCD5C\uC2E0 \uD0DC\uADF8)
1504
+ --to <ref> \uB05D \uCEE4\uBC0B (\uAE30\uBCF8: HEAD)
1505
+ --locale ko,en-US \uB300\uC0C1 \uB85C\uCF00\uC77C (\uC27C\uD45C \uAD6C\uBD84)
1506
+ --apply \uC0DD\uC131 \uD6C4 \uC2A4\uD1A0\uC5B4\uC5D0 \uBC14\uB85C \uC801\uC6A9
1507
+ --no-interactive CI \uBAA8\uB4DC (\uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C)
1508
+ --limit <n> \uCD5C\uB300 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 30)`,
1509
+ check: `${kleur9.bold("mimi-seed check")} \u2014 \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
1510
+
1511
+ \uC635\uC158:
1512
+ --app <id> \uC571 ID \uC9C0\uC815
1513
+ --fail-on-blocker \uBE14\uB85C\uCEE4 \uC788\uC73C\uBA74 exit 1 (CI\uC6A9)`,
1514
+ review: `${kleur9.bold("mimi-seed review")} \u2014 \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
1515
+
1516
+ \uC635\uC158:
1517
+ --text <\uB0B4\uC6A9> \uB9AC\uBDF0 \uC6D0\uBB38 (\uBBF8\uC785\uB825 \uC2DC \uB300\uD654\uD615 \uD504\uB86C\uD504\uD2B8)
1518
+ --rating <1-5> \uBCC4\uC810
1519
+ --tone <tone> friendly / professional / empathetic / brief (\uAE30\uBCF8: friendly)
1520
+ --language <\uCF54\uB4DC> \uB2F5\uBCC0 \uC5B8\uC5B4 (\uAE30\uBCF8: ko)
1521
+ --app-name <\uC774\uB984> \uC571 \uC774\uB984 (\uB9E5\uB77D\uC6A9)
1522
+ --apply \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC
1523
+ --review-id <id> \uB9AC\uBDF0 ID (--apply \uC2DC \uD544\uC694)
1524
+ --package-name <p> \uD328\uD0A4\uC9C0\uBA85 (--apply \uC2DC \uD544\uC694)
1525
+ --no-interactive CI \uBAA8\uB4DC`,
1526
+ deploy: `${kleur9.bold("mimi-seed deploy")} \u2014 \uC571 \uC790\uB3D9 \uBC30\uD3EC (CI \uBE4C\uB4DC \u2192 Play Store/App Store)
1527
+
1528
+ \uC635\uC158:
1529
+ --platform android|ios \uBC30\uD3EC \uD50C\uB7AB\uD3FC (\uAE30\uBCF8: android)
1530
+ --app <id> \uC571 ID \uC9C0\uC815
1531
+ --version-code <n> \uBE4C\uB4DC \uBC88\uD638 \uC9C1\uC811 \uC9C0\uC815 (--skip-build \uC640 \uD568\uAED8)
1532
+ --from <ref> \uCEE4\uBC0B \uBC94\uC704 \uC2DC\uC791 (\uB9B4\uB9AC\uC988 \uB178\uD2B8\uC6A9)
1533
+ --to <ref> \uCEE4\uBC0B \uBC94\uC704 \uB05D (\uAE30\uBCF8: HEAD)
1534
+ --language <\uCF54\uB4DC> \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC5B8\uC5B4 (\uAE30\uBCF8: ko-KR)
1535
+ --dry-run \uC2E4\uC81C \uBC30\uD3EC \uC5C6\uC774 \uD30C\uC774\uD504\uB77C\uC778 \uD14C\uC2A4\uD2B8
1536
+ --yes, -y \uBC30\uD3EC \uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC0DD\uB7B5 (\uC2A4\uD06C\uB9BD\uD2B8/\uC790\uB3D9\uD654\uC6A9)
1537
+ --skip-build CI \uBE4C\uB4DC \uAC74\uB108\uB700 (--version-code \uD544\uC218)
1538
+ --ci jenkins|github|gitlab CI \uAC15\uC81C \uC120\uD0DD (\uAE30\uBCF8: auto)
1539
+ --workflow <file> GitHub workflow \uD30C\uC77C (\uC608: deploy.yml)
1540
+ --ref <branch|tag> GitHub/GitLab \uBE0C\uB79C\uCE58/\uD0DC\uADF8 (\uAE30\uBCF8: main)
1541
+ setup-jenkins / setup-github / setup-gitlab CI \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D`,
1542
+ mcp: `${kleur9.bold("mimi-seed mcp")} \u2014 Claude/Codex MCP \uC5F0\uACB0
1543
+
1544
+ \uC11C\uBE0C\uBA85\uB839:
1545
+ mimi-seed mcp \uD604\uC7AC \uC124\uC815 + \uB4F1\uB85D \uC548\uB0B4
1546
+ mimi-seed mcp claude Claude Code \uB4F1\uB85D \uBA85\uB839 \uCD9C\uB825
1547
+ mimi-seed mcp codex Codex \uB4F1\uB85D \uC548\uB0B4
1548
+ mimi-seed mcp codex --write ~/.codex/config.toml\uC5D0 \uC9C1\uC811 \uAE30\uB85D (\u26A0 \uC2E4\uC81C \uD1A0\uD070 \uD3C9\uBB38 \uC800\uC7A5)`
1549
+ },
1550
+ help: `${kleur9.bold("mimi-seed")} \u2014 Claude Code/Codex\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
1551
+
1552
+ ${kleur9.bold("\uBA85\uB839\uC5B4:")}
1553
+ ${kleur9.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1554
+ ${kleur9.cyan("mimi-seed setup")} \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
1555
+ ${kleur9.cyan("mimi-seed lang")} \uCD9C\uB825 \uC5B8\uC5B4 (ko / en)
1556
+ ${kleur9.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
1557
+ ${kleur9.cyan("mimi-seed auth")} \uC790\uACA9\uC99D\uBA85 \uAC1C\uBCC4 \uC778\uC99D (Google / App Store / Play / Jenkins / CI \u2026)
1558
+ ${kleur9.cyan("mimi-seed firebase")} Firebase \uC571 \uC0DD\uC131\xB7config \uB2E4\uC6B4\uB85C\uB4DC\xB7GA4 \uB9C1\uD06C
1559
+ ${kleur9.cyan("mimi-seed admob")} AdMob \uACC4\uC815\xB7\uC571\xB7\uAD11\uACE0\uB2E8\uC704 \uC870\uD68C \uBC0F \uC0DD\uC131
1560
+ ${kleur9.cyan("mimi-seed ga4")} GA4 property\xB7data stream \uC0DD\uC131\xB7\uC870\uD68C
1561
+ ${kleur9.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
1562
+ ${kleur9.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
1563
+ ${kleur9.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
1564
+ ${kleur9.cyan("mimi-seed review")} \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
1565
+ ${kleur9.cyan("mimi-seed deploy")} \uC571 \uC790\uB3D9 \uBC30\uD3EC (CI \u2192 Play Store/App Store)
1566
+ ${kleur9.cyan("mimi-seed mcp")} Claude/Codex MCP \uC5F0\uACB0 \uC548\uB0B4 \uBC0F Codex \uC124\uC815 \uC4F0\uAE30
1567
+ ${kleur9.cyan("mimi-seed restart")} MCP \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791 (\uAE30\uBCF8: mimi-seed)
1568
+ ${kleur9.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
1569
+
1570
+ ${kleur9.dim("\uAC01 \uBA85\uB839 \uC0C1\uC138 \uC635\uC158:")} ${kleur9.cyan("mimi-seed <command> --help")}
1571
+
1572
+ ${kleur9.bold("\uD658\uACBD\uBCC0\uC218:")}
1573
+ MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
1574
+ MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
1575
+ ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
1576
+ MIMI_SEED_LANG \uCD9C\uB825 \uC5B8\uC5B4 \uAC15\uC81C (ko / en) \u2014 settings.json \uBCF4\uB2E4 \uC6B0\uC120
1577
+ MIMI_SEED_GOOGLE_CLIENT_ID / _SECRET
1578
+ \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)
1579
+ `
1580
+ },
1581
+ {
1582
+ noProjectSignal: "\u26A0 No package.json / app.json / android / ios found. Continuing anyway.",
1583
+ detecting: "\u{1F50D} Detecting apps...",
1584
+ noAppDetected: "No app detected. You can register one manually on the web.",
1585
+ unnamed: "(unnamed)",
1586
+ ciMode: "CI mode: using MIMI_SEED_TOKEN",
1587
+ syncFailed: (text) => "Registration failed: " + text,
1588
+ manifest: (created) => ` Release notes SSOT: ${created ? "created" : "verified"} docs/releases.json`,
1589
+ doneCi: "\u2713 Done (CI mode)",
1590
+ waitingLogin: "\u{1F510} Waiting for browser sign-in...",
1591
+ connectFailed: (msg) => "Connection failed: " + msg,
1592
+ tokenReceived: "\u2713 Token received",
1593
+ saved: (location) => ` Saved: ${location}`,
1594
+ registering: "\u{1F504} Registering apps...",
1595
+ claudeAgent: " Claude agent config: .claude/mimi-seed.md",
1596
+ codexAgent: " Codex agent config: AGENTS.md",
1597
+ ready: "\u2713 Ready.",
1598
+ askLike: "Try asking this in Claude Code or Codex:",
1599
+ ask1: ' "Is my app ready to ship?"',
1600
+ ask2: ' "Write the release notes"',
1601
+ ask3: ' "Show my registered apps"',
1602
+ dashboard: (url) => `Dashboard: ${url}`,
1603
+ localTitle: "\u2500\u2500 Extra local MCP setup (--local) \u2500\u2500",
1604
+ localIntro: "The remote MCP (PAT, read + diagnostics) is done above. The local MCP runs every store write tool directly via Google OAuth (Node 20+).",
1605
+ localStep1: "1) Sign in with Google (Firebase / AdMob / Play / Ads):",
1606
+ localStep2: "2) Register the local MCP server (separate from the remote 'mimi-seed'):",
1607
+ localCodexHint: ' Codex: add [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"] to ~/.codex/config.toml',
1608
+ localStep3: "3) Connect the remaining accounts (App Store / Play / Jenkins / CI / social \u2026):",
1609
+ localSetupHint: " Press [?] on any item to see how to obtain that token.",
1610
+ notConnected: "No Mimi Seed account connected. Run `mimi-seed init`.",
1611
+ statusTitle: "Mimi Seed connection status",
1612
+ statusToken: (prefix, date) => ` Token: ${prefix}\u2026 (${date})`,
1613
+ statusEndpoint: (endpoint) => ` Endpoint: ${endpoint}`,
1614
+ appList: "\u{1F4CB} Apps:",
1615
+ listFailed: (text) => "Lookup failed: " + text,
1616
+ logoutDone: "\u2713 Local config deleted.",
1617
+ logoutRevoke: "Revoke the token on the web: /workspace/api-tokens",
1618
+ codexWritten: "\u2713 Codex MCP configured",
1619
+ codexWriteWarn: " \u26A0 The real token is stored in plain text in config.toml (check the file permissions).",
1620
+ codexVerify: "Reopen Codex and verify with `/mcp` or `codex mcp list`.",
1621
+ codexTitle: "Codex MCP registration",
1622
+ codexAuto: "Automatic:",
1623
+ codexManual: "Manual example (~/.codex/config.toml) \u2014 contains the real token:",
1624
+ claudeTitle: "Claude Code MCP registration \u2014 run this one line as-is:",
1625
+ claudeWarn: " \u26A0 This command contains the real token (it stays in your shell history). The token is also stored in ~/.mimi-seed/config.json.",
1626
+ usage: {
1627
+ init: `${kleur9.bold("mimi-seed init")} \u2014 connect the current project to Mimi Seed
1628
+
1629
+ Auto-detects apps (Expo/Gradle/Info.plist/pbxproj) \u2192 issues a PAT in the browser (remote MCP) \u2192 registers the apps
1630
+ + creates/verifies .claude/mimi-seed.md, AGENTS.md, docs/releases.json.
1631
+
1632
+ Options:
1633
+ --local also sign in with Google OAuth and show local MCP (all store write tools) setup`,
1634
+ setup: `${kleur9.bold("mimi-seed setup")} \u2014 connect the accounts you have, in one pass (guided wizard)
1635
+
1636
+ Shows the connection status first, then asks only about what is not connected yet.
1637
+ Press [?] on any item to learn where and how to obtain that token.
1638
+ Already-connected items are skipped, so you can quit halfway and rerun it later.
1639
+
1640
+ Options:
1641
+ --only <ids> only the given credentials (comma-separated: oauth,appstore,jenkins \u2026)
1642
+ --reconnect <ids> set up again even if already connected
1643
+ --platform android,ios force the platforms (default: auto-detected from the project)
1644
+ --yes, -y ask nothing, just print the status table
1645
+ --non-interactive same as above (for CI)
1646
+ --fail-on-missing exit 1 if a required credential is missing (CI gate)
1647
+
1648
+ ${kleur9.dim("In non-TTY / CI environments it prints the status table only, with no prompts.")}`,
1649
+ lang: `${kleur9.bold("mimi-seed lang")} \u2014 CLI output language (\uD55C\uAD6D\uC5B4 / English)
1650
+
1651
+ mimi-seed lang show current language
1652
+ mimi-seed lang ko \uD55C\uAD6D\uC5B4 (default)
1653
+ mimi-seed lang en English
1654
+
1655
+ ${kleur9.dim("Stored in ~/.mimi-seed/settings.json. MIMI_SEED_LANG takes precedence when set.")}
1656
+ ${kleur9.dim("The setup wizard asks on first run, so you rarely need to type this.")}`,
1657
+ status: `${kleur9.bold("mimi-seed status")} \u2014 connection status + registered apps. No options.`,
1658
+ doctor: `${kleur9.bold("mimi-seed doctor")} \u2014 environment check (token \xB7 Node \xB7 Git \xB7 project \xB7 CI). No options.`,
1659
+ logout: `${kleur9.bold("mimi-seed logout")} \u2014 delete the local config (config.json). No options.`,
1660
+ restart: `${kleur9.bold("mimi-seed restart")} \u2014 restart the MCP server process (default: mimi-seed)
1661
+
1662
+ mimi-seed restart [server-name]`,
1663
+ notes: `${kleur9.bold("mimi-seed notes")} \u2014 generate release notes (git log \u2192 AI \u2192 push to stores)
1664
+
1665
+ Options:
1666
+ --from <ref> start commit/tag (default: latest tag)
1667
+ --to <ref> end commit (default: HEAD)
1668
+ --locale ko,en-US target locales (comma-separated)
1669
+ --apply push to the stores right after generating
1670
+ --no-interactive CI mode (no prompts)
1671
+ --limit <n> max commits (default: 30)`,
1672
+ check: `${kleur9.bold("mimi-seed check")} \u2014 pre-release readiness check
1673
+
1674
+ Options:
1675
+ --app <id> app ID
1676
+ --fail-on-blocker exit 1 if a blocker is found (for CI)`,
1677
+ review: `${kleur9.bold("mimi-seed review")} \u2014 draft a review reply with AI and post it to the Play Store
1678
+
1679
+ Options:
1680
+ --text <content> the review text (interactive prompt if omitted)
1681
+ --rating <1-5> star rating
1682
+ --tone <tone> friendly / professional / empathetic / brief (default: friendly)
1683
+ --language <code> reply language (default: ko)
1684
+ --app-name <name> app name (for context)
1685
+ --apply post the reply to the Play Store
1686
+ --review-id <id> review ID (required with --apply)
1687
+ --package-name <p> package name (required with --apply)
1688
+ --no-interactive CI mode`,
1689
+ deploy: `${kleur9.bold("mimi-seed deploy")} \u2014 automated app release (CI build \u2192 Play Store/App Store)
1690
+
1691
+ Options:
1692
+ --platform android|ios target platform (default: android)
1693
+ --app <id> app ID
1694
+ --version-code <n> set the build number explicitly (with --skip-build)
1695
+ --from <ref> commit range start (for release notes)
1696
+ --to <ref> commit range end (default: HEAD)
1697
+ --language <code> release notes language (default: ko-KR)
1698
+ --dry-run exercise the pipeline without releasing
1699
+ --yes, -y skip the release confirmation prompt (scripts/automation)
1700
+ --skip-build skip the CI build (--version-code required)
1701
+ --ci jenkins|github|gitlab force the CI provider (default: auto)
1702
+ --workflow <file> GitHub workflow file (e.g. deploy.yml)
1703
+ --ref <branch|tag> GitHub/GitLab branch/tag (default: main)
1704
+ setup-jenkins / setup-github / setup-gitlab interactive CI setup`,
1705
+ mcp: `${kleur9.bold("mimi-seed mcp")} \u2014 Claude/Codex MCP connection
1706
+
1707
+ Subcommands:
1708
+ mimi-seed mcp current config + setup instructions
1709
+ mimi-seed mcp claude print the Claude Code registration command
1710
+ mimi-seed mcp codex Codex setup instructions
1711
+ mimi-seed mcp codex --write write straight into ~/.codex/config.toml (\u26A0 stores the real token in plain text)`
1712
+ },
1713
+ help: `${kleur9.bold("mimi-seed")} \u2014 app release ops from Claude Code/Codex
1714
+
1715
+ ${kleur9.bold("Commands:")}
1716
+ ${kleur9.cyan("mimi-seed init")} connect the current project to Mimi Seed
1717
+ ${kleur9.cyan("mimi-seed setup")} connect the accounts you have, in one pass (guided wizard)
1718
+ ${kleur9.cyan("mimi-seed lang")} output language (ko / en)
1719
+ ${kleur9.cyan("mimi-seed status")} connection status + registered apps
1720
+ ${kleur9.cyan("mimi-seed auth")} connect credentials one by one (Google / App Store / Play / Jenkins / CI \u2026)
1721
+ ${kleur9.cyan("mimi-seed firebase")} create Firebase apps, download configs, link GA4
1722
+ ${kleur9.cyan("mimi-seed admob")} list and create AdMob accounts, apps, ad units
1723
+ ${kleur9.cyan("mimi-seed ga4")} create and list GA4 properties and data streams
1724
+ ${kleur9.cyan("mimi-seed doctor")} environment check (token \xB7 Git \xB7 project \xB7 CI)
1725
+ ${kleur9.cyan("mimi-seed check")} pre-release readiness check
1726
+ ${kleur9.cyan("mimi-seed notes")} generate release notes (git log \u2192 AI \u2192 push to stores)
1727
+ ${kleur9.cyan("mimi-seed review")} draft a review reply with AI and post it to the Play Store
1728
+ ${kleur9.cyan("mimi-seed deploy")} automated app release (CI \u2192 Play Store/App Store)
1729
+ ${kleur9.cyan("mimi-seed mcp")} Claude/Codex MCP setup instructions and Codex config writing
1730
+ ${kleur9.cyan("mimi-seed restart")} restart the MCP server process (default: mimi-seed)
1731
+ ${kleur9.cyan("mimi-seed logout")} delete the local config
1732
+
1733
+ ${kleur9.dim("Per-command options:")} ${kleur9.cyan("mimi-seed <command> --help")}
1734
+
1735
+ ${kleur9.bold("Environment variables:")}
1736
+ MIMI_SEED_TOKEN PAT token (headless CI/CD mode)
1737
+ MIMI_SEED_WEB_BASE server address (default: https://mimi-seed.pryzm.gg)
1738
+ ANTHROPIC_API_KEY enable AI note generation (optional)
1739
+ MIMI_SEED_LANG force the output language (ko / en) \u2014 beats settings.json
1740
+ MIMI_SEED_GOOGLE_CLIENT_ID / _SECRET
1741
+ use your own Google OAuth client (otherwise fetched from the web console at sign-in)
1742
+ `
1743
+ }
1744
+ );
1745
+ function log4(msg) {
1031
1746
  process.stdout.write(msg + "\n");
1032
1747
  }
1033
1748
  async function cmdInit(args) {
1034
1749
  const local = args.includes("--local");
1035
1750
  const cwd = process.cwd();
1036
- log3(kleur8.bold("Mimi Seed CLI \u2014 init"));
1037
- log3(kleur8.dim(cwd));
1038
- log3("");
1751
+ log4(kleur9.bold("Mimi Seed CLI \u2014 init"));
1752
+ log4(kleur9.dim(cwd));
1753
+ log4("");
1039
1754
  if (!await hasAnyProjectSignal(cwd)) {
1040
- log3(kleur8.yellow("\u26A0 package.json / app.json / android / ios \uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD569\uB2C8\uB2E4."));
1755
+ log4(kleur9.yellow(M8().noProjectSignal));
1041
1756
  }
1042
- log3("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
1757
+ log4(M8().detecting);
1043
1758
  const hints = await detectHints(cwd);
1044
1759
  if (hints.length === 0) {
1045
- log3(kleur8.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
1760
+ log4(kleur9.yellow(M8().noAppDetected));
1046
1761
  } else {
1047
1762
  for (const h of hints) {
1048
1763
  const tag = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
1049
- log3(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur8.dim(tag)}`);
1764
+ log4(` \u2022 ${h.name ?? M8().unnamed} ${kleur9.dim(tag)}`);
1050
1765
  }
1051
1766
  }
1052
- log3("");
1767
+ log4("");
1053
1768
  if (process.env.MIMI_SEED_TOKEN) {
1054
1769
  const cfg2 = await getEffectiveConfig();
1055
1770
  if (cfg2 && hints.length > 0) {
1056
- log3(kleur8.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
1771
+ log4(kleur9.dim(M8().ciMode));
1057
1772
  const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
1058
1773
  const result = await mcpCall(cfg2.endpoint, cfg2.token, "sync_apps", { hints: payload });
1059
1774
  if (result.isError) {
1060
- log3(kleur8.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
1775
+ log4(kleur9.red(M8().syncFailed(result.text)));
1061
1776
  } else {
1062
- for (const line of result.text.split("\n")) log3(" " + line);
1777
+ for (const line of result.text.split("\n")) log4(" " + line);
1063
1778
  }
1064
1779
  }
1065
1780
  const manifest2 = await ensureReleaseManifest(cwd);
1066
- log3(kleur8.dim(` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${manifest2.created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`));
1067
- log3(kleur8.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
1781
+ log4(kleur9.dim(M8().manifest(manifest2.created)));
1782
+ log4(kleur9.bold(M8().doneCi));
1068
1783
  return;
1069
1784
  }
1070
- log3("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
1785
+ log4(M8().waitingLogin);
1071
1786
  const hostName = os3.hostname().slice(0, 32);
1072
1787
  const name = `cli-${hostName}`;
1073
1788
  const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
1074
1789
  const callback = `http://127.0.0.1:${port}/cb`;
1075
1790
  const connectUrl = `${DEFAULT_WEB_BASE}/cli/connect?callback=${encodeURIComponent(callback)}&name=${encodeURIComponent(name)}`;
1076
- log3(kleur8.dim(` ${connectUrl}`));
1791
+ log4(kleur9.dim(` ${connectUrl}`));
1077
1792
  await open(connectUrl);
1078
1793
  let handshake;
1079
1794
  try {
1080
1795
  handshake = await promise;
1081
1796
  } catch (e) {
1082
- log3(kleur8.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
1797
+ log4(kleur9.red(M8().connectFailed(e.message)));
1083
1798
  process.exit(1);
1084
1799
  }
1085
- log3(kleur8.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
1800
+ log4(kleur9.green(M8().tokenReceived));
1086
1801
  const cfg = {
1087
1802
  token: handshake.token,
1088
1803
  prefix: handshake.prefix,
@@ -1091,18 +1806,18 @@ async function cmdInit(args) {
1091
1806
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1092
1807
  };
1093
1808
  await writeConfig(cfg);
1094
- log3(kleur8.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
1095
- log3("");
1809
+ log4(kleur9.dim(M8().saved(CONFIG_LOCATION)));
1810
+ log4("");
1096
1811
  if (hints.length > 0) {
1097
- log3("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
1812
+ log4(M8().registering);
1098
1813
  const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
1099
1814
  const result = await mcpCall(cfg.endpoint, cfg.token, "sync_apps", { hints: payload });
1100
1815
  if (result.isError) {
1101
- log3(kleur8.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
1816
+ log4(kleur9.red(M8().syncFailed(result.text)));
1102
1817
  } else {
1103
- for (const line of result.text.split("\n")) log3(" " + line);
1818
+ for (const line of result.text.split("\n")) log4(" " + line);
1104
1819
  }
1105
- log3("");
1820
+ log4("");
1106
1821
  }
1107
1822
  const appLines = hints.flatMap((h) => {
1108
1823
  const parts = [
@@ -1151,216 +1866,110 @@ async function cmdInit(args) {
1151
1866
  if (!fs5.existsSync(claudeAgentPath)) {
1152
1867
  fs5.mkdirSync(claudeDir, { recursive: true });
1153
1868
  fs5.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
1154
- log3(kleur8.dim(` Claude \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md`));
1869
+ log4(kleur9.dim(M8().claudeAgent));
1155
1870
  }
1156
1871
  const codexAgentPath = path5.join(cwd, "AGENTS.md");
1157
1872
  if (!fs5.existsSync(codexAgentPath)) {
1158
1873
  fs5.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
1159
- log3(kleur8.dim(` Codex \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: AGENTS.md`));
1874
+ log4(kleur9.dim(M8().codexAgent));
1160
1875
  }
1161
1876
  const manifest = await ensureReleaseManifest(cwd);
1162
- log3(kleur8.dim(` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${manifest.created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`));
1163
- log3(kleur8.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
1164
- log3("");
1165
- log3("Claude Code \uB610\uB294 Codex\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
1166
- log3(kleur8.cyan(' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'));
1167
- log3(kleur8.cyan(' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"'));
1168
- log3(kleur8.cyan(' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"'));
1169
- log3("");
1170
- log3(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur8.underline(DEFAULT_WEB_BASE + "/apps")}`);
1171
- log3("");
1877
+ log4(kleur9.dim(M8().manifest(manifest.created)));
1878
+ log4(kleur9.bold(M8().ready));
1879
+ log4("");
1880
+ log4(M8().askLike);
1881
+ log4(kleur9.cyan(M8().ask1));
1882
+ log4(kleur9.cyan(M8().ask2));
1883
+ log4(kleur9.cyan(M8().ask3));
1884
+ log4("");
1885
+ log4(M8().dashboard(kleur9.underline(DEFAULT_WEB_BASE + "/apps")));
1886
+ log4("");
1172
1887
  printMcpSetup(cfg);
1173
1888
  if (local) {
1174
- log3("");
1175
- log3(kleur8.bold("\u2500\u2500 \uB85C\uCEEC MCP \uCD94\uAC00 \uC124\uC815 (--local) \u2500\u2500"));
1176
- log3(kleur8.dim("\uC6D0\uACA9 MCP(PAT, \uC77D\uAE30\xB7\uC9C4\uB2E8)\uB294 \uC704\uC5D0\uC11C \uB05D. \uB85C\uCEEC MCP\uB294 Google OAuth\uB85C \uC2A4\uD1A0\uC5B4 \uC4F0\uAE30 \uB3C4\uAD6C \uC804\uCCB4\uB97C \uC9C1\uC811 \uC2E4\uD589\uD569\uB2C8\uB2E4 (Node 20+)."));
1177
- log3("");
1178
- log3("1) Google \uB85C\uADF8\uC778 (Firebase / AdMob / Play / Ads):");
1889
+ log4("");
1890
+ log4(kleur9.bold(M8().localTitle));
1891
+ log4(kleur9.dim(M8().localIntro));
1892
+ log4("");
1893
+ log4(M8().localStep1);
1179
1894
  await cmdAuth(["login"]);
1180
- log3("");
1181
- log3("2) \uB85C\uCEEC MCP \uC11C\uBC84 \uB4F1\uB85D (\uC6D0\uACA9 'mimi-seed' \uC640 \uBCC4\uAC1C):");
1182
- log3(kleur8.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp"));
1183
- log3(kleur8.dim(' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"]'));
1184
- log3("");
1185
- log3("3) \uB098\uBA38\uC9C0 \uACC4\uC815 \uC5F0\uACB0 (App Store / Play / Jenkins / CI / \uC18C\uC15C \u2026):");
1186
- log3(kleur8.cyan(" mimi-seed setup"));
1187
- log3(kleur8.dim(" \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 \uD1A0\uD070 \uBC1C\uAE09 \uBC29\uBC95\uC744 \uC54C\uB824\uC90D\uB2C8\uB2E4."));
1895
+ log4("");
1896
+ log4(M8().localStep2);
1897
+ log4(kleur9.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp"));
1898
+ log4(kleur9.dim(M8().localCodexHint));
1899
+ log4("");
1900
+ log4(M8().localStep3);
1901
+ log4(kleur9.cyan(" mimi-seed setup"));
1902
+ log4(kleur9.dim(M8().localSetupHint));
1188
1903
  }
1189
1904
  }
1190
1905
  async function cmdStatus() {
1191
1906
  const cfg = await getEffectiveConfig();
1192
1907
  if (!cfg) {
1193
- log3(kleur8.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
1908
+ log4(kleur9.yellow(M8().notConnected));
1194
1909
  process.exit(1);
1195
1910
  }
1196
- log3(kleur8.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
1197
- log3(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
1198
- log3(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
1199
- log3("");
1200
- log3("\u{1F4CB} \uC571 \uBAA9\uB85D:");
1911
+ log4(kleur9.bold(M8().statusTitle));
1912
+ log4(M8().statusToken(cfg.prefix, cfg.createdAt.slice(0, 10)));
1913
+ log4(M8().statusEndpoint(cfg.endpoint));
1914
+ log4("");
1915
+ log4(M8().appList);
1201
1916
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
1202
1917
  if (r.isError) {
1203
- log3(kleur8.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
1918
+ log4(kleur9.red(M8().listFailed(r.text)));
1204
1919
  process.exit(1);
1205
1920
  }
1206
- for (const line of r.text.split("\n")) log3(" " + line);
1921
+ for (const line of r.text.split("\n")) log4(" " + line);
1207
1922
  }
1208
1923
  async function cmdLogout() {
1209
1924
  await deleteConfig();
1210
- log3(kleur8.green("\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC."));
1211
- log3(kleur8.dim("\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens"));
1925
+ log4(kleur9.green(M8().logoutDone));
1926
+ log4(kleur9.dim(M8().logoutRevoke));
1212
1927
  }
1213
1928
  async function cmdMcp(args) {
1214
1929
  const target = args[0] ?? "help";
1215
1930
  const cfg = await getEffectiveConfig();
1216
1931
  if (!cfg) {
1217
- log3(kleur8.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
1932
+ log4(kleur9.yellow(M8().notConnected));
1218
1933
  process.exit(1);
1219
1934
  }
1220
1935
  if (target === "codex") {
1221
1936
  if (args.includes("--write")) {
1222
1937
  const configPath = await writeCodexMcpConfig(cfg);
1223
- log3(kleur8.green("\u2713 Codex MCP \uC124\uC815 \uC644\uB8CC"));
1224
- log3(kleur8.dim(` ${configPath}`));
1225
- log3(kleur8.dim(" \u26A0 config.toml\uC5D0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3C9\uBB38\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4 (\uD30C\uC77C \uAD8C\uD55C \uD655\uC778 \uAD8C\uC7A5)."));
1226
- log3("");
1227
- log3("Codex\uB97C \uC0C8\uB85C \uC5F4\uACE0 `/mcp` \uB610\uB294 `codex mcp list`\uB85C \uD655\uC778\uD558\uC138\uC694.");
1938
+ log4(kleur9.green(M8().codexWritten));
1939
+ log4(kleur9.dim(` ${configPath}`));
1940
+ log4(kleur9.dim(M8().codexWriteWarn));
1941
+ log4("");
1942
+ log4(M8().codexVerify);
1228
1943
  return;
1229
1944
  }
1230
- log3(kleur8.bold("Codex MCP \uB4F1\uB85D"));
1231
- log3("");
1232
- log3("\uC790\uB3D9 \uB4F1\uB85D:");
1233
- log3(kleur8.cyan(" mimi-seed mcp codex --write"));
1234
- log3("");
1235
- log3("\uC218\uB3D9 \uB4F1\uB85D \uC608\uC2DC (~/.codex/config.toml) \u2014 \uC2E4\uC81C \uD1A0\uD070 \uD3EC\uD568:");
1236
- log3(`[mcp_servers.mimi-seed]
1945
+ log4(kleur9.bold(M8().codexTitle));
1946
+ log4("");
1947
+ log4(M8().codexAuto);
1948
+ log4(kleur9.cyan(" mimi-seed mcp codex --write"));
1949
+ log4("");
1950
+ log4(M8().codexManual);
1951
+ log4(`[mcp_servers.mimi-seed]
1237
1952
  url = "${cfg.endpoint}"
1238
1953
  enabled = true
1239
1954
  http_headers = { Authorization = "Bearer ${cfg.token}" }`);
1240
1955
  return;
1241
1956
  }
1242
1957
  if (target === "claude") {
1243
- log3(kleur8.bold("Claude Code MCP \uB4F1\uB85D \u2014 \uC544\uB798 \uD55C \uC904\uC744 \uADF8\uB300\uB85C \uC2E4\uD589\uD558\uC138\uC694:"));
1244
- log3(kleur8.cyan(` ${claudeMcpAddCommand(cfg)}`));
1245
- log3(kleur8.dim(" \u26A0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3EC\uD568\uB41C \uBA85\uB839\uC785\uB2C8\uB2E4 (\uC178 \uD788\uC2A4\uD1A0\uB9AC\uC5D0 \uB0A8\uC74C). \uD1A0\uD070\uC740 ~/.mimi-seed/config.json \uC5D0\uB3C4 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."));
1958
+ log4(kleur9.bold(M8().claudeTitle));
1959
+ log4(kleur9.cyan(` ${claudeMcpAddCommand(cfg)}`));
1960
+ log4(kleur9.dim(M8().claudeWarn));
1246
1961
  return;
1247
1962
  }
1248
1963
  printMcpSetup(cfg);
1249
1964
  }
1250
- var CMD_USAGE = {
1251
- init: `${kleur8.bold("mimi-seed init")} \u2014 \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1252
-
1253
- \uC571 \uC790\uB3D9 \uAC10\uC9C0(Expo/Gradle/Info.plist/pbxproj) \u2192 \uBE0C\uB77C\uC6B0\uC800 PAT \uBC1C\uAE09(\uC6D0\uACA9 MCP) \u2192 \uC571 \uB4F1\uB85D
1254
- + .claude/mimi-seed.md, AGENTS.md, docs/releases.json \uC0DD\uC131/\uD655\uC778.
1255
-
1256
- \uC635\uC158:
1257
- --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`,
1258
- setup: `${kleur8.bold("mimi-seed setup")} \u2014 \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
1259
-
1260
- \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.
1261
- \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.
1262
- \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.
1263
-
1264
- \uC635\uC158:
1265
- --only <ids> \uC9C0\uC815\uD55C \uC790\uACA9\uC99D\uBA85\uB9CC (\uC27C\uD45C \uAD6C\uBD84: oauth,appstore,jenkins \u2026)
1266
- --reconnect <ids> \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uB3C4 \uB2E4\uC2DC \uC124\uC815
1267
- --platform android,ios \uD50C\uB7AB\uD3FC \uAC15\uC81C \uC9C0\uC815 (\uAE30\uBCF8: \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uB3D9 \uAC10\uC9C0)
1268
- --yes, -y \uC544\uBB34\uAC83\uB3C4 \uBB3B\uC9C0 \uC54A\uACE0 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825
1269
- --non-interactive \uC704\uC640 \uB3D9\uC77C (CI \uC6A9)
1270
- --fail-on-missing \uD544\uC218 \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC73C\uBA74 exit 1 (CI \uAC8C\uC774\uD2B8)
1271
-
1272
- ${kleur8.dim("\uBE44TTY / CI \uD658\uACBD\uC5D0\uC11C\uB294 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825\uD55C\uB2E4.")}`,
1273
- status: `${kleur8.bold("mimi-seed status")} \u2014 \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D. \uC635\uC158 \uC5C6\uC74C.`,
1274
- doctor: `${kleur8.bold("mimi-seed doctor")} \u2014 \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Node\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI). \uC635\uC158 \uC5C6\uC74C.`,
1275
- logout: `${kleur8.bold("mimi-seed logout")} \u2014 \uB85C\uCEEC \uC124\uC815(config.json) \uC0AD\uC81C. \uC635\uC158 \uC5C6\uC74C.`,
1276
- restart: `${kleur8.bold("mimi-seed restart")} \u2014 MCP \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791 (\uAE30\uBCF8: mimi-seed)
1277
-
1278
- mimi-seed restart [server-name]`,
1279
- notes: `${kleur8.bold("mimi-seed notes")} \u2014 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
1280
-
1281
- \uC635\uC158:
1282
- --from <ref> \uC2DC\uC791 \uCEE4\uBC0B/\uD0DC\uADF8 (\uAE30\uBCF8: \uCD5C\uC2E0 \uD0DC\uADF8)
1283
- --to <ref> \uB05D \uCEE4\uBC0B (\uAE30\uBCF8: HEAD)
1284
- --locale ko,en-US \uB300\uC0C1 \uB85C\uCF00\uC77C (\uC27C\uD45C \uAD6C\uBD84)
1285
- --apply \uC0DD\uC131 \uD6C4 \uC2A4\uD1A0\uC5B4\uC5D0 \uBC14\uB85C \uC801\uC6A9
1286
- --no-interactive CI \uBAA8\uB4DC (\uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C)
1287
- --limit <n> \uCD5C\uB300 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 30)`,
1288
- check: `${kleur8.bold("mimi-seed check")} \u2014 \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
1289
-
1290
- \uC635\uC158:
1291
- --app <id> \uC571 ID \uC9C0\uC815
1292
- --fail-on-blocker \uBE14\uB85C\uCEE4 \uC788\uC73C\uBA74 exit 1 (CI\uC6A9)`,
1293
- review: `${kleur8.bold("mimi-seed review")} \u2014 \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
1294
-
1295
- \uC635\uC158:
1296
- --text <\uB0B4\uC6A9> \uB9AC\uBDF0 \uC6D0\uBB38 (\uBBF8\uC785\uB825 \uC2DC \uB300\uD654\uD615 \uD504\uB86C\uD504\uD2B8)
1297
- --rating <1-5> \uBCC4\uC810
1298
- --tone <tone> friendly / professional / empathetic / brief (\uAE30\uBCF8: friendly)
1299
- --language <\uCF54\uB4DC> \uB2F5\uBCC0 \uC5B8\uC5B4 (\uAE30\uBCF8: ko)
1300
- --app-name <\uC774\uB984> \uC571 \uC774\uB984 (\uB9E5\uB77D\uC6A9)
1301
- --apply \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC
1302
- --review-id <id> \uB9AC\uBDF0 ID (--apply \uC2DC \uD544\uC694)
1303
- --package-name <p> \uD328\uD0A4\uC9C0\uBA85 (--apply \uC2DC \uD544\uC694)
1304
- --no-interactive CI \uBAA8\uB4DC`,
1305
- deploy: `${kleur8.bold("mimi-seed deploy")} \u2014 \uC571 \uC790\uB3D9 \uBC30\uD3EC (CI \uBE4C\uB4DC \u2192 Play Store/App Store)
1306
-
1307
- \uC635\uC158:
1308
- --platform android|ios \uBC30\uD3EC \uD50C\uB7AB\uD3FC (\uAE30\uBCF8: android)
1309
- --app <id> \uC571 ID \uC9C0\uC815
1310
- --version-code <n> \uBE4C\uB4DC \uBC88\uD638 \uC9C1\uC811 \uC9C0\uC815 (--skip-build \uC640 \uD568\uAED8)
1311
- --from <ref> \uCEE4\uBC0B \uBC94\uC704 \uC2DC\uC791 (\uB9B4\uB9AC\uC988 \uB178\uD2B8\uC6A9)
1312
- --to <ref> \uCEE4\uBC0B \uBC94\uC704 \uB05D (\uAE30\uBCF8: HEAD)
1313
- --language <\uCF54\uB4DC> \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC5B8\uC5B4 (\uAE30\uBCF8: ko-KR)
1314
- --dry-run \uC2E4\uC81C \uBC30\uD3EC \uC5C6\uC774 \uD30C\uC774\uD504\uB77C\uC778 \uD14C\uC2A4\uD2B8
1315
- --yes, -y \uBC30\uD3EC \uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC0DD\uB7B5 (\uC2A4\uD06C\uB9BD\uD2B8/\uC790\uB3D9\uD654\uC6A9)
1316
- --skip-build CI \uBE4C\uB4DC \uAC74\uB108\uB700 (--version-code \uD544\uC218)
1317
- --ci jenkins|github|gitlab CI \uAC15\uC81C \uC120\uD0DD (\uAE30\uBCF8: auto)
1318
- --workflow <file> GitHub workflow \uD30C\uC77C (\uC608: deploy.yml)
1319
- --ref <branch|tag> GitHub/GitLab \uBE0C\uB79C\uCE58/\uD0DC\uADF8 (\uAE30\uBCF8: main)
1320
- setup-jenkins / setup-github / setup-gitlab CI \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D`,
1321
- mcp: `${kleur8.bold("mimi-seed mcp")} \u2014 Claude/Codex MCP \uC5F0\uACB0
1322
-
1323
- \uC11C\uBE0C\uBA85\uB839:
1324
- mimi-seed mcp \uD604\uC7AC \uC124\uC815 + \uB4F1\uB85D \uC548\uB0B4
1325
- mimi-seed mcp claude Claude Code \uB4F1\uB85D \uBA85\uB839 \uCD9C\uB825
1326
- mimi-seed mcp codex Codex \uB4F1\uB85D \uC548\uB0B4
1327
- mimi-seed mcp codex --write ~/.codex/config.toml\uC5D0 \uC9C1\uC811 \uAE30\uB85D (\u26A0 \uC2E4\uC81C \uD1A0\uD070 \uD3C9\uBB38 \uC800\uC7A5)`
1328
- };
1329
1965
  function printCommandHelp(cmd) {
1330
- const usage = CMD_USAGE[cmd];
1966
+ const usage = M8().usage[cmd];
1331
1967
  if (!usage) return false;
1332
- log3(usage);
1968
+ log4(usage);
1333
1969
  return true;
1334
1970
  }
1335
1971
  function printHelp() {
1336
- log3(`${kleur8.bold("mimi-seed")} \u2014 Claude Code/Codex\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
1337
-
1338
- ${kleur8.bold("\uBA85\uB839\uC5B4:")}
1339
- ${kleur8.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1340
- ${kleur8.cyan("mimi-seed setup")} \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
1341
- ${kleur8.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
1342
- ${kleur8.cyan("mimi-seed auth")} \uC790\uACA9\uC99D\uBA85 \uAC1C\uBCC4 \uC778\uC99D (Google / App Store / Play / Jenkins / CI \u2026)
1343
- ${kleur8.cyan("mimi-seed firebase")} Firebase \uC571 \uC0DD\uC131\xB7config \uB2E4\uC6B4\uB85C\uB4DC\xB7GA4 \uB9C1\uD06C
1344
- ${kleur8.cyan("mimi-seed admob")} AdMob \uACC4\uC815\xB7\uC571\xB7\uAD11\uACE0\uB2E8\uC704 \uC870\uD68C \uBC0F \uC0DD\uC131
1345
- ${kleur8.cyan("mimi-seed ga4")} GA4 property\xB7data stream \uC0DD\uC131\xB7\uC870\uD68C
1346
- ${kleur8.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
1347
- ${kleur8.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
1348
- ${kleur8.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
1349
- ${kleur8.cyan("mimi-seed review")} \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
1350
- ${kleur8.cyan("mimi-seed deploy")} \uC571 \uC790\uB3D9 \uBC30\uD3EC (CI \u2192 Play Store/App Store)
1351
- ${kleur8.cyan("mimi-seed mcp")} Claude/Codex MCP \uC5F0\uACB0 \uC548\uB0B4 \uBC0F Codex \uC124\uC815 \uC4F0\uAE30
1352
- ${kleur8.cyan("mimi-seed restart")} MCP \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791 (\uAE30\uBCF8: mimi-seed)
1353
- ${kleur8.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
1354
-
1355
- ${kleur8.dim("\uAC01 \uBA85\uB839 \uC0C1\uC138 \uC635\uC158:")} ${kleur8.cyan("mimi-seed <command> --help")}
1356
-
1357
- ${kleur8.bold("\uD658\uACBD\uBCC0\uC218:")}
1358
- MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
1359
- MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
1360
- ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
1361
- MIMI_SEED_GOOGLE_CLIENT_ID / _SECRET
1362
- \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)
1363
- `);
1972
+ log4(M8().help);
1364
1973
  }
1365
1974
  async function main() {
1366
1975
  const cmd = process.argv[2];
@@ -1376,6 +1985,9 @@ async function main() {
1376
1985
  case "setup":
1377
1986
  await cmdSetup(restArgs);
1378
1987
  break;
1988
+ case "lang":
1989
+ await cmdLang(restArgs);
1990
+ break;
1379
1991
  case "status":
1380
1992
  await cmdStatus();
1381
1993
  break;
@@ -1421,13 +2033,13 @@ async function main() {
1421
2033
  printHelp();
1422
2034
  break;
1423
2035
  default:
1424
- log3(kleur8.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
2036
+ log4(kleur9.red(t().common.unknownCommand(cmd)));
1425
2037
  printHelp();
1426
2038
  process.exit(1);
1427
2039
  }
1428
2040
  } catch (e) {
1429
- log3(kleur8.red(`\uC624\uB958: ${e.message}`));
1430
- if (process.env.DEBUG) log3(e.stack ?? "");
2041
+ log4(kleur9.red(t().common.error(e.message)));
2042
+ if (process.env.DEBUG) log4(e.stack ?? "");
1431
2043
  process.exit(1);
1432
2044
  }
1433
2045
  }