mimi-seed 0.2.11 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +223 -49
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
 
6
6
  // src/index.ts
7
7
  import os7 from "os";
8
- import fs8 from "fs";
9
- import path8 from "path";
8
+ import fs10 from "fs";
9
+ import path10 from "path";
10
10
  import kleur9 from "kleur";
11
11
  import open from "open";
12
12
 
@@ -255,9 +255,9 @@ async function getEffectiveConfig() {
255
255
  }
256
256
 
257
257
  // src/doctor.ts
258
- import fs3 from "fs";
258
+ import fs4 from "fs";
259
259
  import os2 from "os";
260
- import path3 from "path";
260
+ import path4 from "path";
261
261
  import kleur from "kleur";
262
262
 
263
263
  // src/git.ts
@@ -300,6 +300,33 @@ function formatCommitsForPrompt(commits) {
300
300
  return commits.map((c) => `- ${c.message} (${c.author}, ${c.date.slice(0, 10)})`).join("\n");
301
301
  }
302
302
 
303
+ // src/project-manifest.ts
304
+ import fs3 from "fs";
305
+ import path3 from "path";
306
+ var MANIFEST_FILENAME = ".mimi-seed.json";
307
+ function findProjectManifest(startDir = process.cwd(), maxDepth = 8) {
308
+ let dir = path3.resolve(startDir);
309
+ for (let i = 0; i <= maxDepth; i++) {
310
+ const candidate = path3.join(dir, MANIFEST_FILENAME);
311
+ if (fs3.existsSync(candidate)) {
312
+ try {
313
+ const obj = JSON.parse(fs3.readFileSync(candidate, "utf-8"));
314
+ if (obj && typeof obj === "object") return { manifest: obj, filePath: candidate };
315
+ } catch {
316
+ }
317
+ return null;
318
+ }
319
+ const parent = path3.dirname(dir);
320
+ if (parent === dir) break;
321
+ dir = parent;
322
+ }
323
+ return null;
324
+ }
325
+ function manifestServiceEntries(m) {
326
+ const svc = m.services ?? {};
327
+ return Object.keys(svc).filter((k) => svc[k] != null).map((k) => [k, svc[k]]);
328
+ }
329
+
303
330
  // src/doctor.ts
304
331
  function ok(label, detail = "") {
305
332
  process.stdout.write(` ${kleur.green("\u2713")} ${label}${detail ? kleur.dim(" " + detail) : ""}
@@ -317,6 +344,20 @@ function section(title) {
317
344
  process.stdout.write("\n" + kleur.dim(`\u2500\u2500 ${title} \u2500\u2500
318
345
  `));
319
346
  }
347
+ function manifestDetail(id, svc) {
348
+ const parts = [];
349
+ if (id === "bigquery") {
350
+ if (svc.projectId) parts.push(svc.projectId);
351
+ if (svc.dataset) parts.push(svc.dataset);
352
+ } else if (id === "playstore" && svc.packageName) {
353
+ parts.push(svc.packageName);
354
+ } else if (id === "appstore" && svc.keyId) {
355
+ parts.push(`keyId ${svc.keyId}`);
356
+ } else if (id === "jenkins" && svc.url) {
357
+ parts.push(svc.url);
358
+ }
359
+ return parts.join(" / ");
360
+ }
320
361
  async function cmdDoctor() {
321
362
  const cwd = process.cwd();
322
363
  process.stdout.write(kleur.bold("mimi-seed doctor\n\n"));
@@ -339,33 +380,64 @@ async function cmdDoctor() {
339
380
  }
340
381
  }
341
382
  section("\uB85C\uCEEC MCP \uC790\uACA9\uC99D\uBA85 (~/.mimi-seed)");
342
- const credDir = path3.join(os2.homedir(), ".mimi-seed");
343
- const hasFile = (name) => fs3.existsSync(path3.join(credDir, name));
383
+ const credDir = path4.join(os2.homedir(), ".mimi-seed");
384
+ const hasFile = (name) => fs4.existsSync(path4.join(credDir, name));
344
385
  const hasPrefix = (prefix) => {
345
386
  try {
346
- return fs3.readdirSync(credDir).some((f) => f.startsWith(prefix));
387
+ return fs4.readdirSync(credDir).some((f) => f.startsWith(prefix));
347
388
  } catch {
348
389
  return false;
349
390
  }
350
391
  };
351
392
  const hasPlaySa = hasFile("play-service-account.json") || (() => {
352
393
  try {
353
- return fs3.readdirSync(path3.join(credDir, "play-service-accounts")).some((f) => f.endsWith(".json"));
394
+ return fs4.readdirSync(path4.join(credDir, "play-service-accounts")).some((f) => f.endsWith(".json"));
354
395
  } catch {
355
396
  return false;
356
397
  }
357
398
  })();
399
+ const oauthPresent = hasFile("tokens.json");
400
+ const appstorePresent = hasFile("appstore.json");
401
+ const bigquerySaPresent = hasPrefix("bigquery");
358
402
  const creds = [
359
- ["Google OAuth (Firebase/AdMob/Play/Ads)", hasFile("tokens.json"), "mimi-seed auth login"],
360
- ["App Store Connect", hasFile("appstore.json"), "mimi-seed auth appstore"],
403
+ ["Google OAuth (Firebase/AdMob/Play/Ads)", oauthPresent, "mimi-seed auth login"],
404
+ ["App Store Connect", appstorePresent, "mimi-seed auth appstore"],
361
405
  ["Play \uC11C\uBE44\uC2A4 \uACC4\uC815 (\uC120\uD0DD \u2014 OAuth\uB85C\uB3C4 \uAC00\uB2A5)", hasPlaySa, "mimi-seed auth playstore"],
362
- ["BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815", hasPrefix("bigquery"), "mimi-seed auth bigquery"]
406
+ ["BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815", bigquerySaPresent, "mimi-seed auth bigquery"]
363
407
  ];
364
408
  for (const [label, present, cmd] of creds) {
365
409
  if (present) ok(label);
366
410
  else warn(label, `\u2192 ${cmd}`);
367
411
  }
368
412
  process.stdout.write(kleur.dim(" OAuth \uD1A0\uD070 \uC2E0\uC120\uB3C4 \uD655\uC778: mimi-seed auth status\n"));
413
+ const loaded = findProjectManifest(cwd);
414
+ if (loaded) {
415
+ const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? "\uC774 \uD504\uB85C\uC81D\uD2B8";
416
+ section(`${projName} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`);
417
+ const connectedOf = {
418
+ oauth: oauthPresent,
419
+ bigquery: bigquerySaPresent || oauthPresent,
420
+ playstore: hasPlaySa || oauthPresent,
421
+ appstore: appstorePresent,
422
+ jenkins: false
423
+ // mimi-seed 로컬 자격증명 대상 아님(별도 MCP) — note 로만 안내
424
+ };
425
+ const fixOf = {
426
+ oauth: "mimi-seed auth login",
427
+ bigquery: "mimi-seed auth bigquery",
428
+ playstore: "mimi-seed auth playstore",
429
+ appstore: "mimi-seed auth appstore",
430
+ jenkins: "claude mcp add virgm-jenkins -s user"
431
+ };
432
+ for (const [id, svc] of manifestServiceEntries(loaded.manifest)) {
433
+ const required = svc.required !== false;
434
+ const connected = connectedOf[id];
435
+ const detail = manifestDetail(id, svc);
436
+ if (connected) ok(id, detail);
437
+ else if (!required) warn(`${id} (\uC120\uD0DD)`, svc.note ?? detail);
438
+ else fail(id, `\u2192 ${fixOf[id]}${detail ? " " + detail : ""}`);
439
+ }
440
+ }
369
441
  section("\uB85C\uCEEC \uD658\uACBD");
370
442
  const nodeVer = process.version;
371
443
  const [, major] = nodeVer.match(/v(\d+)/) ?? [];
@@ -837,12 +909,12 @@ async function cmdReview(argv) {
837
909
 
838
910
  // src/auth.ts
839
911
  import { spawn } from "child_process";
840
- import fs4 from "fs";
912
+ import fs5 from "fs";
841
913
  import os3 from "os";
842
- import path4 from "path";
914
+ import path5 from "path";
843
915
  import kleur5 from "kleur";
844
916
  var MCP_PKG = "@yoonion/mimi-seed-mcp";
845
- var MIMI_DIR = path4.join(os3.homedir(), ".mimi-seed");
917
+ var MIMI_DIR = path5.join(os3.homedir(), ".mimi-seed");
846
918
  function log(msg) {
847
919
  process.stdout.write(msg + "\n");
848
920
  }
@@ -886,11 +958,11 @@ async function runMcpBin(bin, extraArgs) {
886
958
  });
887
959
  }
888
960
  function fileExists(name) {
889
- return fs4.existsSync(path4.join(MIMI_DIR, name));
961
+ return fs5.existsSync(path5.join(MIMI_DIR, name));
890
962
  }
891
963
  function anyFileStarting(prefix) {
892
964
  try {
893
- return fs4.readdirSync(MIMI_DIR).some((f) => f.startsWith(prefix));
965
+ return fs5.readdirSync(MIMI_DIR).some((f) => f.startsWith(prefix));
894
966
  } catch {
895
967
  return false;
896
968
  }
@@ -949,30 +1021,61 @@ function exitWith(code) {
949
1021
  if (code !== 0) process.exit(code);
950
1022
  }
951
1023
 
1024
+ // src/cloud.ts
1025
+ import { spawn as spawn2 } from "child_process";
1026
+ var MCP_PKG2 = "@yoonion/mimi-seed-mcp";
1027
+ async function runMcpBin2(bin, extraArgs) {
1028
+ return new Promise((resolve) => {
1029
+ const child = spawn2("npx", ["-y", `${MCP_PKG2}`, bin, ...extraArgs], {
1030
+ stdio: "inherit",
1031
+ shell: true
1032
+ });
1033
+ child.on("error", (e) => {
1034
+ process.stderr.write(`
1035
+ \u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
1036
+ `);
1037
+ resolve(1);
1038
+ });
1039
+ child.on("exit", (code) => resolve(code ?? 0));
1040
+ });
1041
+ }
1042
+ function exitWith2(code) {
1043
+ if (code !== 0) process.exit(code);
1044
+ }
1045
+ async function cmdFirebase(args) {
1046
+ exitWith2(await runMcpBin2("mimi-seed-firebase", args));
1047
+ }
1048
+ async function cmdAdmob(args) {
1049
+ exitWith2(await runMcpBin2("mimi-seed-admob", args));
1050
+ }
1051
+ async function cmdGa4(args) {
1052
+ exitWith2(await runMcpBin2("mimi-seed-ga4", args));
1053
+ }
1054
+
952
1055
  // src/deploy.ts
953
1056
  import kleur6 from "kleur";
954
1057
  import * as readline from "readline";
955
1058
 
956
1059
  // src/ci-providers.ts
957
- import fs5 from "fs";
958
- import path5 from "path";
1060
+ import fs6 from "fs";
1061
+ import path6 from "path";
959
1062
  import os4 from "os";
960
- var CI_CONFIG_PATH = path5.join(os4.homedir(), ".mimi-seed", "ci.json");
1063
+ var CI_CONFIG_PATH = path6.join(os4.homedir(), ".mimi-seed", "ci.json");
961
1064
  function loadCiProviderConfig() {
962
1065
  try {
963
- return JSON.parse(fs5.readFileSync(CI_CONFIG_PATH, "utf-8"));
1066
+ return JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
964
1067
  } catch {
965
1068
  return null;
966
1069
  }
967
1070
  }
968
1071
  function saveCiProviderConfig(cfg) {
969
- const dir = path5.dirname(CI_CONFIG_PATH);
970
- if (!fs5.existsSync(dir)) {
971
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
1072
+ const dir = path6.dirname(CI_CONFIG_PATH);
1073
+ if (!fs6.existsSync(dir)) {
1074
+ fs6.mkdirSync(dir, { recursive: true, mode: 448 });
972
1075
  }
973
- fs5.writeFileSync(CI_CONFIG_PATH, JSON.stringify(cfg, null, 2));
1076
+ fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(cfg, null, 2));
974
1077
  if (process.platform !== "win32") {
975
- fs5.chmodSync(CI_CONFIG_PATH, 384);
1078
+ fs6.chmodSync(CI_CONFIG_PATH, 384);
976
1079
  }
977
1080
  }
978
1081
  function ghBase(cfg) {
@@ -1473,17 +1576,17 @@ async function cmdDeploy(argv) {
1473
1576
 
1474
1577
  // src/mcp-restart.ts
1475
1578
  import { execSync as execSync2 } from "child_process";
1476
- import fs6 from "fs";
1579
+ import fs7 from "fs";
1477
1580
  import os5 from "os";
1478
- import path6 from "path";
1581
+ import path7 from "path";
1479
1582
  import kleur7 from "kleur";
1480
1583
  function log3(msg) {
1481
1584
  process.stdout.write(msg + "\n");
1482
1585
  }
1483
1586
  function readClaudeJson() {
1484
- const p = path6.join(os5.homedir(), ".claude.json");
1587
+ const p = path7.join(os5.homedir(), ".claude.json");
1485
1588
  try {
1486
- return JSON.parse(fs6.readFileSync(p, "utf8"));
1589
+ return JSON.parse(fs7.readFileSync(p, "utf8"));
1487
1590
  } catch {
1488
1591
  return {};
1489
1592
  }
@@ -1570,8 +1673,8 @@ async function cmdRestart(args) {
1570
1673
  }
1571
1674
 
1572
1675
  // src/mcp-config.ts
1573
- import fs7 from "fs/promises";
1574
- import path7 from "path";
1676
+ import fs8 from "fs/promises";
1677
+ import path8 from "path";
1575
1678
  import os6 from "os";
1576
1679
  import kleur8 from "kleur";
1577
1680
  var SERVER_NAME = "mimi-seed";
@@ -1612,20 +1715,63 @@ function printMcpSetup(cfg) {
1612
1715
  );
1613
1716
  }
1614
1717
  async function writeCodexMcpConfig(cfg) {
1615
- const configDir = path7.join(os6.homedir(), ".codex");
1616
- const configPath = path7.join(configDir, "config.toml");
1617
- await fs7.mkdir(configDir, { recursive: true });
1718
+ const configDir = path8.join(os6.homedir(), ".codex");
1719
+ const configPath = path8.join(configDir, "config.toml");
1720
+ await fs8.mkdir(configDir, { recursive: true });
1618
1721
  let current = "";
1619
1722
  try {
1620
- current = await fs7.readFile(configPath, "utf8");
1723
+ current = await fs8.readFile(configPath, "utf8");
1621
1724
  } catch {
1622
1725
  current = "";
1623
1726
  }
1624
1727
  const next = replaceTomlBlock(current, `mcp_servers.${SERVER_NAME}`, codexBlock(cfg));
1625
- await fs7.writeFile(configPath, next);
1728
+ await fs8.writeFile(configPath, next);
1626
1729
  return configPath;
1627
1730
  }
1628
1731
 
1732
+ // src/release-manifest.ts
1733
+ import fs9 from "fs/promises";
1734
+ import path9 from "path";
1735
+ var RELEASE_MANIFEST_RELATIVE_PATH = path9.join("docs", "releases.json");
1736
+ var RELEASE_MANIFEST_TEMPLATE = {
1737
+ $schema: {
1738
+ _comment: "Mimi Seed release notes SSOT. Store notes, App Store What's New, in-app announcements, and app-version rollout metadata can share this file.",
1739
+ fields: {
1740
+ date: "ISO 8601 release/build date.",
1741
+ android: "Google Play release notes, 500 characters or fewer per locale.",
1742
+ ios: "App Store What's New, 4000 characters or fewer.",
1743
+ appVersion: {
1744
+ minVersion: "Optional minimum supported app version.",
1745
+ latestVersion: "Optional latest recommended app version.",
1746
+ android: "Optional Android override: { minVersion?, latestVersion? }.",
1747
+ ios: "Optional iOS override: { minVersion?, latestVersion? }."
1748
+ },
1749
+ announcement: {
1750
+ title: "Optional in-app announcement title.",
1751
+ content: "Optional in-app announcement body.",
1752
+ type: "info | update | event | maintenance",
1753
+ pinned: "Optional boolean."
1754
+ },
1755
+ highlights: "Optional internal changelog bullets."
1756
+ }
1757
+ },
1758
+ versions: {}
1759
+ };
1760
+ async function ensureReleaseManifest(cwd) {
1761
+ const filePath = path9.join(cwd, RELEASE_MANIFEST_RELATIVE_PATH);
1762
+ try {
1763
+ await fs9.access(filePath);
1764
+ return { path: filePath, created: false };
1765
+ } catch {
1766
+ }
1767
+ await fs9.mkdir(path9.dirname(filePath), { recursive: true });
1768
+ await fs9.writeFile(filePath, `${JSON.stringify(RELEASE_MANIFEST_TEMPLATE, null, 2)}
1769
+ `, {
1770
+ mode: 420
1771
+ });
1772
+ return { path: filePath, created: true };
1773
+ }
1774
+
1629
1775
  // src/index.ts
1630
1776
  var DEFAULT_WEB_BASE = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
1631
1777
  var DEFAULT_MCP_ENDPOINT = `${DEFAULT_WEB_BASE}/api/mcp`;
@@ -1664,6 +1810,8 @@ async function cmdInit(args) {
1664
1810
  for (const line of result.text.split("\n")) log4(" " + line);
1665
1811
  }
1666
1812
  }
1813
+ const manifest2 = await ensureReleaseManifest(cwd);
1814
+ log4(kleur9.dim(` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${manifest2.created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`));
1667
1815
  log4(kleur9.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
1668
1816
  return;
1669
1817
  }
@@ -1718,12 +1866,24 @@ async function cmdInit(args) {
1718
1866
  "Mimi Seed MCP\uAC00 \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
1719
1867
  "Google Play \xB7 App Store \xB7 Firebase \xB7 AdMob\uC744 \uB3C4\uAD6C\uB85C \uC9C1\uC811 \uC81C\uC5B4\uD569\uB2C8\uB2E4.",
1720
1868
  "",
1869
+ "## \uC138\uC158 \uC2DC\uC791",
1870
+ "",
1871
+ "1. \uCD9C\uC2DC/\uC2A4\uD1A0\uC5B4/Firebase/AdMob \uC694\uCCAD\uC740 \uBA3C\uC800 `mimi_seed_status`\uB85C \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uD655\uC778",
1872
+ "2. \uC778\uC99D \uB204\uB77D\uC774\uBA74 `mimi_seed_auth_start` \uB610\uB294 \uC544\uB798 \uB85C\uCEEC \uC778\uC99D \uBA85\uB839\uC744 \uC548\uB0B4",
1873
+ '3. Claude Code\uC5D0\uC11C \uB3C4\uAD6C schema\uAC00 deferred \uC0C1\uD0DC\uB77C\uBA74 \uD544\uC694\uD55C \uB3C4\uAD6C\uB97C `ToolSearch(query="select:<tool>[,<tool>...]")`\uB85C \uBA3C\uC800 \uB85C\uB4DC',
1874
+ "",
1721
1875
  "## \uCD9C\uC2DC \uC694\uCCAD \uCC98\uB9AC \uC21C\uC11C",
1722
1876
  "",
1723
- "1. \uD56D\uC0C1 `playstore_check_submission_risks` / `appstore_check_submission_risks` \uB85C \uBE14\uB85C\uCEE4 \uBA3C\uC800 \uD655\uC778",
1724
- "2. \uB9B4\uB9AC\uC988 \uB178\uD2B8: `generate_release_notes_from_commits` \u2192 \uC0AC\uC6A9\uC790 \uD655\uC778 \uD6C4 \uC801\uC6A9",
1725
- "3. \uC2A4\uD1A0\uC5B4 **\uC4F0\uAE30** \uC791\uC5C5(submit, apply, reply)\uC740 \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uC790 \uBA85\uC2DC \uB3D9\uC758 \uD6C4 \uC2E4\uD589",
1726
- "4. \uC644\uB8CC \uD6C4 \uACB0\uACFC \uC694\uC57D \uC81C\uACF5",
1877
+ "1. \uD56D\uC0C1 `playstore_check_submission_risks` / `appstore_check_submission_risks` \uB85C \uBE14\uB85C\uCEE4 \uD655\uC778",
1878
+ "2. \uB9B4\uB9AC\uC988 \uB178\uD2B8\uB294 `docs/releases.json`\uC744 SSOT\uB85C \uD655\uC778/\uC791\uC131 \u2192 \uC0AC\uC6A9\uC790 \uD655\uC778 \uD6C4 \uC801\uC6A9",
1879
+ "3. \uC2A4\uD1A0\uC5B4 **\uC4F0\uAE30** \uC791\uC5C5(submit, apply, reply, delete)\uC740 \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uC790 \uBA85\uC2DC \uB3D9\uC758 \uD6C4 \uC2E4\uD589",
1880
+ "4. \uCD9C\uC2DC \uC644\uB8CC \uD6C4 \uC801\uC6A9 \uACB0\uACFC\uC640 \uC2E4\uD328 \uC9C0\uC810 \uC694\uC57D",
1881
+ "",
1882
+ "## \uC778\uC99D \uBCF5\uAD6C",
1883
+ "",
1884
+ "- Google/Firebase/AdMob/Play OAuth: `npx -y @yoonion/mimi-seed-mcp mimi-seed-auth`",
1885
+ "- App Store Connect: `npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth`",
1886
+ "- Play service account: `npx -y @yoonion/mimi-seed-mcp mimi-seed-playstore-auth`",
1727
1887
  "",
1728
1888
  "## \uC571 \uC815\uBCF4",
1729
1889
  ...appLines.length > 0 ? appLines : [" (mimi-seed status \uB85C \uD655\uC778)"],
@@ -1734,18 +1894,20 @@ async function cmdInit(args) {
1734
1894
  "- `/mimi-seed:health` \u2014 \uC5F0\uACB0 \uC0C1\uD0DC \uBE60\uB978 \uD655\uC778",
1735
1895
  "- `/mimi-seed:review-inbox` \u2014 \uBBF8\uB2F5\uBCC0 \uB9AC\uBDF0 \uB2F5\uBCC0"
1736
1896
  ].join("\n");
1737
- const claudeDir = path8.join(cwd, ".claude");
1738
- const claudeAgentPath = path8.join(claudeDir, "mimi-seed.md");
1739
- if (!fs8.existsSync(claudeAgentPath)) {
1740
- fs8.mkdirSync(claudeDir, { recursive: true });
1741
- fs8.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
1897
+ const claudeDir = path10.join(cwd, ".claude");
1898
+ const claudeAgentPath = path10.join(claudeDir, "mimi-seed.md");
1899
+ if (!fs10.existsSync(claudeAgentPath)) {
1900
+ fs10.mkdirSync(claudeDir, { recursive: true });
1901
+ fs10.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
1742
1902
  log4(kleur9.dim(` Claude \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md`));
1743
1903
  }
1744
- const codexAgentPath = path8.join(cwd, "AGENTS.md");
1745
- if (!fs8.existsSync(codexAgentPath)) {
1746
- fs8.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
1904
+ const codexAgentPath = path10.join(cwd, "AGENTS.md");
1905
+ if (!fs10.existsSync(codexAgentPath)) {
1906
+ fs10.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
1747
1907
  log4(kleur9.dim(` Codex \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: AGENTS.md`));
1748
1908
  }
1909
+ const manifest = await ensureReleaseManifest(cwd);
1910
+ log4(kleur9.dim(` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${manifest.created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`));
1749
1911
  log4(kleur9.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
1750
1912
  log4("");
1751
1913
  log4("Claude Code \uB610\uB294 Codex\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
@@ -1833,7 +1995,7 @@ var CMD_USAGE = {
1833
1995
  init: `${kleur9.bold("mimi-seed init")} \u2014 \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1834
1996
 
1835
1997
  \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
1836
- + .claude/mimi-seed.md, AGENTS.md \uC0DD\uC131.
1998
+ + .claude/mimi-seed.md, AGENTS.md, docs/releases.json \uC0DD\uC131/\uD655\uC778.
1837
1999
 
1838
2000
  \uC635\uC158:
1839
2001
  --local \uCD94\uAC00\uB85C Google OAuth \uB85C\uADF8\uC778 + \uB85C\uCEEC 110+ tool MCP \uB4F1\uB85D \uC548\uB0B4`,
@@ -1906,6 +2068,9 @@ ${kleur9.bold("\uBA85\uB839\uC5B4:")}
1906
2068
  ${kleur9.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1907
2069
  ${kleur9.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
1908
2070
  ${kleur9.cyan("mimi-seed auth")} \uB85C\uCEEC \uC778\uC99D (Google OAuth / App Store / Play / BigQuery)
2071
+ ${kleur9.cyan("mimi-seed firebase")} Firebase \uC571 \uC0DD\uC131\xB7config \uB2E4\uC6B4\uB85C\uB4DC\xB7GA4 \uB9C1\uD06C
2072
+ ${kleur9.cyan("mimi-seed admob")} AdMob \uACC4\uC815\xB7\uC571\xB7\uAD11\uACE0\uB2E8\uC704 \uC870\uD68C \uBC0F \uC0DD\uC131
2073
+ ${kleur9.cyan("mimi-seed ga4")} GA4 property\xB7data stream \uC0DD\uC131\xB7\uC870\uD68C
1909
2074
  ${kleur9.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
1910
2075
  ${kleur9.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
1911
2076
  ${kleur9.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
@@ -1952,6 +2117,15 @@ async function main() {
1952
2117
  case "auth":
1953
2118
  await cmdAuth(restArgs);
1954
2119
  break;
2120
+ case "firebase":
2121
+ await cmdFirebase(restArgs);
2122
+ break;
2123
+ case "admob":
2124
+ await cmdAdmob(restArgs);
2125
+ break;
2126
+ case "ga4":
2127
+ await cmdGa4(restArgs);
2128
+ break;
1955
2129
  case "deploy":
1956
2130
  await cmdDeploy(restArgs);
1957
2131
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mimi-seed",
3
- "version": "0.2.11",
3
+ "version": "0.4.0",
4
4
  "description": "Mimi Seed CLI — Claude Code와 Codex에서 앱 출시 운영을 관리합니다.",
5
5
  "bin": {
6
6
  "mimi-seed": "dist/index.js"