mimi-seed 0.19.10 → 0.19.11

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  catalog
4
- } from "./chunk-ZGMPOJRY.js";
4
+ } from "./chunk-TQHLWSFR.js";
5
5
 
6
6
  // src/mcp-client.ts
7
7
  var M = catalog(
@@ -118,6 +118,7 @@ var ko = {
118
118
  serverOk: "Mimi Seed \uC11C\uBC84 \uC5F0\uACB0\uB428",
119
119
  appCount: (n) => `\uC571 ${n}\uAC1C`,
120
120
  unknownService: (id) => `${id} (\uC54C \uC218 \uC5C6\uB294 \uC11C\uBE44\uC2A4)`,
121
+ credentialMismatch: (field, expected, actual) => `${field} \uBD88\uC77C\uCE58 \u2014 \uC694\uAD6C\uAC12 ${expected}, \uD604\uC7AC\uAC12 ${actual ?? "\uC5C6\uC74C"}`,
121
122
  credsHint: " \uC804\uBD80 \uC5F0\uACB0\uD558\uAE30: mimi-seed setup \xB7 OAuth \uC2E0\uC120\uB3C4: mimi-seed auth status\n",
122
123
  nodeTooOld: (v) => `${v} \u2014 v20 \uC774\uC0C1 \uD544\uC694 (.nvmrc \uCC38\uACE0)`,
123
124
  gitRepo: "Git \uC800\uC7A5\uC18C",
@@ -126,7 +127,7 @@ var ko = {
126
127
  noGit: "Git \uC800\uC7A5\uC18C \uC5C6\uC74C",
127
128
  noGitDetail: "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00",
128
129
  noApp: "\uC571 \uAC10\uC9C0 \uC5C6\uC74C",
129
- noAppDetail: "app.json / build.gradle / Info.plist \uC5C6\uC74C",
130
+ noAppDetail: "Expo / Gradle / Xcode / Unity \uC571 \uC124\uC815 \uC5C6\uC74C",
130
131
  unnamed: "(\uC774\uB984 \uBBF8\uC0C1)",
131
132
  requirements: (proj) => `${proj} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`,
132
133
  thisProject: "\uC774 \uD504\uB85C\uC81D\uD2B8"
@@ -220,6 +221,7 @@ MIMI_SEED_LANG takes precedence when set.`,
220
221
  serverOk: "Connected to Mimi Seed",
221
222
  appCount: (n) => `${n} app(s)`,
222
223
  unknownService: (id) => `${id} (unknown service)`,
224
+ credentialMismatch: (field, expected, actual) => `${field} mismatch \u2014 expected ${expected}, current ${actual ?? "missing"}`,
223
225
  credsHint: " Connect everything: mimi-seed setup \xB7 OAuth freshness: mimi-seed auth status\n",
224
226
  nodeTooOld: (v) => `${v} \u2014 v20+ required (see .nvmrc)`,
225
227
  gitRepo: "Git repository",
@@ -228,7 +230,7 @@ MIMI_SEED_LANG takes precedence when set.`,
228
230
  noGit: "Not a git repository",
229
231
  noGitDetail: "mimi-seed notes is unavailable",
230
232
  noApp: "No app detected",
231
- noAppDetail: "no app.json / build.gradle / Info.plist",
233
+ noAppDetail: "no Expo / Gradle / Xcode / Unity app configuration",
232
234
  unnamed: "(unnamed)",
233
235
  requirements: (proj) => `${proj} requirements (.mimi-seed.json)`,
234
236
  thisProject: "This project"
@@ -5,7 +5,7 @@ import {
5
5
  resolveLang,
6
6
  t,
7
7
  writeSettings
8
- } from "./chunk-ZGMPOJRY.js";
8
+ } from "./chunk-TQHLWSFR.js";
9
9
 
10
10
  // src/setup.ts
11
11
  import kleur2 from "kleur";
@@ -242,7 +242,14 @@ var CREDENTIALS = [
242
242
  docsAnchor: "app-store-connect",
243
243
  detect: (home) => {
244
244
  const cfg = readJson(home, "appstore.json");
245
- return cfg ? { present: true, detail: cfg.keyId ? `keyId ${cfg.keyId}` : void 0 } : { present: false };
245
+ return cfg ? {
246
+ present: true,
247
+ detail: cfg.keyId ? `keyId ${cfg.keyId}` : void 0,
248
+ identity: Object.fromEntries([
249
+ ["keyId", cfg.keyId],
250
+ ["issuerId", cfg.issuerId]
251
+ ].filter((entry) => typeof entry[1] === "string"))
252
+ } : { present: false };
246
253
  }
247
254
  },
248
255
  {
@@ -769,6 +776,37 @@ async function pathExists(p) {
769
776
  return false;
770
777
  }
771
778
  }
779
+ async function importedJsonObjects(configPath, text, root) {
780
+ const result = /* @__PURE__ */ new Map();
781
+ const imports = [
782
+ ...text.matchAll(/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s+["']([^"']+\.json)["']/g),
783
+ ...text.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*require\(\s*["']([^"']+\.json)["']\s*\)/g)
784
+ ];
785
+ for (const match of imports) {
786
+ if (!match[2].startsWith(".")) continue;
787
+ const candidate = path5.resolve(path5.dirname(configPath), match[2]);
788
+ const relative = path5.relative(root, candidate);
789
+ if (relative.startsWith(`..${path5.sep}`) || relative === ".." || path5.isAbsolute(relative)) continue;
790
+ const jsonText = await readIfExists(candidate);
791
+ if (!jsonText) continue;
792
+ try {
793
+ const json = JSON.parse(jsonText);
794
+ if (json && typeof json === "object" && !Array.isArray(json)) {
795
+ result.set(match[1], json);
796
+ }
797
+ } catch {
798
+ }
799
+ }
800
+ return result;
801
+ }
802
+ function importedMember(text, block, field, imports) {
803
+ const match = new RegExp(
804
+ `\\b${block}\\s*:\\s*\\{[\\s\\S]{0,5000}?\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)\\.([A-Za-z_$][\\w$]*)`
805
+ ).exec(text);
806
+ if (!match) return void 0;
807
+ const value = imports.get(match[1])?.[match[2]];
808
+ return typeof value === "string" ? value : void 0;
809
+ }
772
810
  async function walk(root, match, maxDepth = 5) {
773
811
  const found = [];
774
812
  const skipDirs = /* @__PURE__ */ new Set([
@@ -823,6 +861,17 @@ async function detectHints(cwd) {
823
861
  } catch {
824
862
  }
825
863
  }
864
+ for (const fname of ["app.config.js", "app.config.cjs", "app.config.mjs", "app.config.ts"]) {
865
+ const configPath = path5.join(cwd, fname);
866
+ const txt = await readIfExists(configPath);
867
+ if (!txt) continue;
868
+ const imports = await importedJsonObjects(configPath, txt, cwd);
869
+ const pkg = txt.match(/\bandroid\s*:\s*\{[\s\S]{0,5000}?\bpackage\s*:\s*["']([^"']+)["']/)?.[1] ?? importedMember(txt, "android", "package", imports);
870
+ const bid = txt.match(/\bios\s*:\s*\{[\s\S]{0,5000}?\bbundleIdentifier\s*:\s*["']([^"']+)["']/)?.[1] ?? importedMember(txt, "ios", "bundleIdentifier", imports);
871
+ if (pkg || bid) {
872
+ hints.push({ packageName: pkg, bundleId: bid, source: [fname] });
873
+ }
874
+ }
826
875
  const gradleFiles = await walk(
827
876
  cwd,
828
877
  (n) => n === "build.gradle" || n === "build.gradle.kts",
@@ -867,6 +916,34 @@ async function detectHints(cwd) {
867
916
  }
868
917
  }
869
918
  }
919
+ const unitySettings = path5.join(cwd, "ProjectSettings", "ProjectSettings.asset");
920
+ const unityText = await readIfExists(unitySettings);
921
+ if (unityText) {
922
+ const lines = unityText.split(/\r?\n/);
923
+ const identifierStart = lines.findIndex((line) => /^\s*applicationIdentifier:\s*$/.test(line));
924
+ const baseIndent = identifierStart >= 0 ? lines[identifierStart].match(/^\s*/)?.[0].length ?? 0 : 0;
925
+ let packageName;
926
+ let bundleId;
927
+ if (identifierStart >= 0) {
928
+ for (const line of lines.slice(identifierStart + 1)) {
929
+ if (!line.trim()) continue;
930
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
931
+ if (indent <= baseIndent) break;
932
+ const entry = line.match(/^\s+(Android|iPhone|iOS):\s*([^\s#]+)\s*$/);
933
+ if (entry?.[1] === "Android") packageName = entry[2];
934
+ if (entry && entry[1] !== "Android") bundleId = entry[2];
935
+ }
936
+ }
937
+ const name = unityText.match(/^\s*productName:\s*(.+?)\s*$/m)?.[1];
938
+ if (packageName || bundleId) {
939
+ hints.push({
940
+ name,
941
+ packageName,
942
+ bundleId,
943
+ source: [path5.relative(cwd, unitySettings)]
944
+ });
945
+ }
946
+ }
870
947
  const pkgJson = await readIfExists(path5.join(cwd, "package.json"));
871
948
  if (pkgJson) {
872
949
  try {
@@ -903,7 +980,7 @@ async function detectHints(cwd) {
903
980
  return merged.filter((h) => h.packageName || h.bundleId);
904
981
  }
905
982
  async function hasAnyProjectSignal(cwd) {
906
- return await pathExists(path5.join(cwd, "package.json")) || await pathExists(path5.join(cwd, "app.json")) || await pathExists(path5.join(cwd, "android")) || await pathExists(path5.join(cwd, "ios"));
983
+ return await pathExists(path5.join(cwd, "package.json")) || await pathExists(path5.join(cwd, "app.json")) || await pathExists(path5.join(cwd, "android")) || await pathExists(path5.join(cwd, "ios")) || await pathExists(path5.join(cwd, "ProjectSettings", "ProjectSettings.asset"));
907
984
  }
908
985
 
909
986
  // src/deploy.ts
@@ -1595,7 +1672,7 @@ async function cmdDeploy(argv) {
1595
1672
  }
1596
1673
  let appId = args.appId;
1597
1674
  if (!appId) {
1598
- const { mcpCall } = await import("./mcp-client-DZNDKAGQ.js");
1675
+ const { mcpCall } = await import("./mcp-client-L5BQ5VG4.js");
1599
1676
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
1600
1677
  if (!r.isError) {
1601
1678
  try {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  mcpCall
4
- } from "./chunk-I3C5DIUA.js";
4
+ } from "./chunk-2FKWHED2.js";
5
5
  import {
6
6
  CONFIG_LOCATION,
7
7
  CREDENTIALS,
@@ -22,14 +22,14 @@ import {
22
22
  runMcpBin,
23
23
  tryCredById,
24
24
  writeConfig
25
- } from "./chunk-KY6LI3CK.js";
25
+ } from "./chunk-UKNCSE4V.js";
26
26
  import {
27
27
  catalog,
28
28
  isLang,
29
29
  resolveLang,
30
30
  t,
31
31
  writeSettings
32
- } from "./chunk-ZGMPOJRY.js";
32
+ } from "./chunk-TQHLWSFR.js";
33
33
 
34
34
  // src/index.ts
35
35
  import os3 from "os";
@@ -176,6 +176,16 @@ function manifestDetail(id, svc) {
176
176
  }
177
177
  return parts.join(" / ");
178
178
  }
179
+ function manifestCredentialMismatch(id, svc, identity) {
180
+ if (id !== "appstore") return null;
181
+ for (const field of ["keyId", "issuerId"]) {
182
+ const expected = svc[field];
183
+ if (expected && identity?.[field] !== expected) {
184
+ return { field, expected, actual: identity?.[field] };
185
+ }
186
+ }
187
+ return null;
188
+ }
179
189
  async function cmdDoctor() {
180
190
  const cwd = process.cwd();
181
191
  const m = t().doctor;
@@ -225,7 +235,10 @@ async function cmdDoctor() {
225
235
  continue;
226
236
  }
227
237
  const connected = isSatisfied(spec, detected);
228
- if (connected) ok(id, detail);
238
+ const mismatch = manifestCredentialMismatch(id, svc, detected.get(spec.id)?.identity);
239
+ if (connected && mismatch) {
240
+ fail(id, `${m.credentialMismatch(mismatch.field, mismatch.expected, mismatch.actual)} \u2192 ${spec.fix}`);
241
+ } else if (connected) ok(id, detail);
229
242
  else if (!required) warn(`${id} (${t().common.optional})`, svc.note ?? detail);
230
243
  else fail(id, `\u2192 ${spec.fix}${detail ? " " + detail : ""}`);
231
244
  }
@@ -335,6 +348,10 @@ var BILLING_MODULE = /com\.android\.billingclient:billing(?:-ktx)?/;
335
348
  var LITERAL_DEPENDENCY = /com\.android\.billingclient:billing(?:-ktx)?:([0-9]+(?:\.[0-9A-Za-z_-]+){0,3})/g;
336
349
  var VARIABLE_DEPENDENCY = /com\.android\.billingclient:billing(?:-ktx)?:\$\{?([A-Za-z_][A-Za-z0-9_.-]*)\}?/g;
337
350
  var VERSION_ASSIGNMENT = /(?:^|\s)([A-Za-z_][A-Za-z0-9_.-]*)\s*(?:=|:)\s*["']([0-9]+(?:\.[0-9A-Za-z_-]+){0,3})["']/gm;
351
+ var KNOWN_OPENIAP_BILLING = /* @__PURE__ */ new Map([
352
+ ["2.1.0", { module: "com.android.billingclient:billing-ktx", version: "8.3.0" }],
353
+ ["2.4.1", { module: "com.android.billingclient:billing", version: "9.1.0" }]
354
+ ]);
338
355
  var BILLING_SUPPORT_SCHEDULE = [
339
356
  { major: 5, submissionDeadline: "2024-08-31", extensionDeadline: "2024-11-01" },
340
357
  { major: 6, submissionDeadline: "2025-08-31", extensionDeadline: "2025-11-01" },
@@ -365,7 +382,7 @@ async function walk(root, maxDepth = 7) {
365
382
  for (const entry of entries) {
366
383
  if (entry.isDirectory()) {
367
384
  if (!SKIP_DIRS.has(entry.name)) await visit(path.join(dir, entry.name), depth + 1);
368
- } else if (entry.isFile() && (entry.name === "build.gradle" || entry.name === "build.gradle.kts" || entry.name === "libs.versions.toml" || entry.name === "package.json")) {
385
+ } else if (entry.isFile() && (entry.name === "build.gradle" || entry.name === "build.gradle.kts" || entry.name === "mainTemplate.gradle" || entry.name === "baseProjectTemplate.gradle" || entry.name === "launcherTemplate.gradle" || entry.name === "libs.versions.toml" || entry.name === "package.json")) {
369
386
  result.push(path.join(dir, entry.name));
370
387
  }
371
388
  }
@@ -466,6 +483,12 @@ async function reactNativeIapEvidence(root, manifestFile, manifestText, resolveT
466
483
  source: "unresolved"
467
484
  };
468
485
  }
486
+ let installedVersion = declaredVersion;
487
+ try {
488
+ const installedManifest = JSON.parse(await fs.readFile(path.join(installedDir, "package.json"), "utf8"));
489
+ if (typeof installedManifest.version === "string") installedVersion = installedManifest.version;
490
+ } catch {
491
+ }
469
492
  const directCandidates = [
470
493
  path.join(installedDir, "android", "build.gradle"),
471
494
  path.join(installedDir, "android", "build.gradle.kts")
@@ -479,7 +502,7 @@ async function reactNativeIapEvidence(root, manifestFile, manifestText, resolveT
479
502
  file: relativeManifest,
480
503
  module: direct[0].slice(0, direct[0].lastIndexOf(":")),
481
504
  version: direct[1],
482
- expression: `react-native-iap ${declaredVersion} native dependency`,
505
+ expression: `react-native-iap ${installedVersion} native dependency`,
483
506
  source: "transitive"
484
507
  };
485
508
  }
@@ -496,16 +519,26 @@ async function reactNativeIapEvidence(root, manifestFile, manifestText, resolveT
496
519
  return {
497
520
  file: relativeManifest,
498
521
  module: "com.android.billingclient:billing",
499
- expression: `react-native-iap ${declaredVersion} detected; transitive Billing version is unresolved`,
522
+ expression: `react-native-iap ${installedVersion} detected; transitive Billing version is unresolved`,
500
523
  source: "unresolved"
501
524
  };
502
525
  }
503
526
  const coordinate = `io.github.hyochan.openiap:openiap-google:${openIapVersion}`;
527
+ const known = KNOWN_OPENIAP_BILLING.get(openIapVersion);
504
528
  if (!resolveTransitive) {
529
+ if (known) {
530
+ return {
531
+ file: relativeManifest,
532
+ module: known.module,
533
+ version: known.version,
534
+ expression: `react-native-iap ${installedVersion} -> ${coordinate} (embedded Maven metadata)`,
535
+ source: "transitive"
536
+ };
537
+ }
505
538
  return {
506
539
  file: relativeManifest,
507
540
  module: "com.android.billingclient:billing",
508
- expression: `react-native-iap ${declaredVersion} -> ${coordinate}; transitive lookup unavailable in repository-only mode`,
541
+ expression: `react-native-iap ${installedVersion} -> ${coordinate}; transitive lookup unavailable in repository-only mode`,
509
542
  source: "unresolved"
510
543
  };
511
544
  }
@@ -516,16 +549,25 @@ async function reactNativeIapEvidence(root, manifestFile, manifestText, resolveT
516
549
  file: relativeManifest,
517
550
  module: resolved.module,
518
551
  version: resolved.version,
519
- expression: `react-native-iap ${declaredVersion} -> ${coordinate}`,
552
+ expression: `react-native-iap ${installedVersion} -> ${coordinate}`,
520
553
  source: "transitive"
521
554
  };
522
555
  }
523
556
  } catch {
524
557
  }
558
+ if (known) {
559
+ return {
560
+ file: relativeManifest,
561
+ module: known.module,
562
+ version: known.version,
563
+ expression: `react-native-iap ${installedVersion} -> ${coordinate} (embedded Maven metadata fallback)`,
564
+ source: "transitive"
565
+ };
566
+ }
525
567
  return {
526
568
  file: relativeManifest,
527
569
  module: "com.android.billingclient:billing",
528
- expression: `react-native-iap ${declaredVersion} -> ${coordinate}; Maven Billing version lookup failed`,
570
+ expression: `react-native-iap ${installedVersion} -> ${coordinate}; Maven Billing version lookup failed`,
529
571
  source: "unresolved"
530
572
  };
531
573
  }
@@ -754,12 +796,55 @@ async function walk2(root, maxDepth = 7) {
754
796
  return files;
755
797
  }
756
798
  function isRelevantFile(name) {
757
- return name === "app.json" || name === "app.config.json" || /^app\.config\.(?:js|cjs|mjs|ts)$/.test(name) || name === "build.gradle" || name === "build.gradle.kts" || name === "libs.versions.toml" || name === "gradle.properties" || name === "AndroidManifest.xml" || name === "Info.plist" || name === "project.pbxproj" || name === "package.json";
799
+ return name === "app.json" || name === "app.config.json" || /^app\.config\.(?:js|cjs|mjs|ts)$/.test(name) || name === "build.gradle" || name === "build.gradle.kts" || name === "libs.versions.toml" || name === "gradle.properties" || name === "AndroidManifest.xml" || name === "Info.plist" || name === "project.pbxproj" || name === "ProjectSettings.asset" || name === "package.json";
758
800
  }
759
801
  function unique(values) {
760
802
  return [...new Set(values.filter(Boolean))].sort();
761
803
  }
762
- function parseExpo(files) {
804
+ function unityApplicationIdentifiers(text) {
805
+ const lines = text.split(/\r?\n/);
806
+ const start = lines.findIndex((line) => /^\s*applicationIdentifier:\s*$/.test(line));
807
+ if (start < 0) return {};
808
+ const baseIndent = lines[start].match(/^\s*/)?.[0].length ?? 0;
809
+ const result = {};
810
+ for (const line of lines.slice(start + 1)) {
811
+ if (!line.trim()) continue;
812
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
813
+ if (indent <= baseIndent) break;
814
+ const entry = line.match(/^\s+(Android|iPhone|iOS):\s*([^\s#]+)\s*$/);
815
+ if (entry?.[1] === "Android") result.android = entry[2];
816
+ if (entry && entry[1] !== "Android") result.ios = entry[2];
817
+ }
818
+ return result;
819
+ }
820
+ async function readStaticJsonImports(file, root) {
821
+ const result = /* @__PURE__ */ new Map();
822
+ const imports = [
823
+ ...file.text.matchAll(/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+\.json)['"]/g),
824
+ ...file.text.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*require\(\s*['"]([^'"]+\.json)['"]\s*\)/g)
825
+ ];
826
+ for (const match of imports) {
827
+ if (!match[2].startsWith(".")) continue;
828
+ const candidate = path2.resolve(path2.dirname(file.absolute), match[2]);
829
+ const relative = path2.relative(root, candidate);
830
+ if (relative.startsWith(`..${path2.sep}`) || relative === ".." || path2.isAbsolute(relative)) continue;
831
+ try {
832
+ const json = JSON.parse(await fs2.readFile(candidate, "utf8"));
833
+ if (json && typeof json === "object" && !Array.isArray(json)) {
834
+ result.set(match[1], json);
835
+ }
836
+ } catch {
837
+ }
838
+ }
839
+ return result;
840
+ }
841
+ function resolveJsonMember(text, block, field, imports) {
842
+ const expression = new RegExp(`\\b${block}\\s*:\\s*\\{[\\s\\S]{0,5000}?\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)\\.([A-Za-z_$][\\w$]*)`).exec(text);
843
+ if (!expression) return void 0;
844
+ const value = imports.get(expression[1])?.[expression[2]];
845
+ return typeof value === "string" ? value : void 0;
846
+ }
847
+ async function parseExpo(files, root) {
763
848
  const androidPackageNames = [];
764
849
  const iosBundleIds = [];
765
850
  const platforms = /* @__PURE__ */ new Set();
@@ -778,9 +863,12 @@ function parseExpo(files) {
778
863
  } catch {
779
864
  const android = file.text.match(/\bandroid\s*:\s*\{[\s\S]{0,3000}?\bpackage\s*:\s*['"]([^'"]+)['"]/);
780
865
  const ios = file.text.match(/\bios\s*:\s*\{[\s\S]{0,3000}?\bbundleIdentifier\s*:\s*['"]([^'"]+)['"]/);
781
- if (android?.[1] || ios?.[1] || /\bexpo\s*:/.test(file.text)) detected = true;
782
- if (android?.[1]) androidPackageNames.push(android[1]);
783
- if (ios?.[1]) iosBundleIds.push(ios[1]);
866
+ const imports = await readStaticJsonImports(file, root);
867
+ const importedAndroid = resolveJsonMember(file.text, "android", "package", imports);
868
+ const importedIos = resolveJsonMember(file.text, "ios", "bundleIdentifier", imports);
869
+ if (android?.[1] || ios?.[1] || importedAndroid || importedIos || /\bexpo\s*:/.test(file.text)) detected = true;
870
+ if (android?.[1] || importedAndroid) androidPackageNames.push(android?.[1] ?? importedAndroid);
871
+ if (ios?.[1] || importedIos) iosBundleIds.push(ios?.[1] ?? importedIos);
784
872
  }
785
873
  }
786
874
  for (const file of files.filter((candidate) => candidate.relative.endsWith("package.json"))) {
@@ -793,8 +881,8 @@ function parseExpo(files) {
793
881
  }
794
882
  return { androidPackageNames, iosBundleIds, platforms, detected };
795
883
  }
796
- function detectProject(files) {
797
- const expo = parseExpo(files);
884
+ async function detectProject(files, root) {
885
+ const expo = await parseExpo(files, root);
798
886
  const gradleFiles = files.filter((file) => /build\.gradle(?:\.kts)?$/.test(file.relative));
799
887
  const pbxFiles = files.filter((file) => file.relative.endsWith("project.pbxproj"));
800
888
  const plistFiles = files.filter((file) => file.relative.endsWith("Info.plist"));
@@ -803,13 +891,28 @@ function detectProject(files) {
803
891
  const versionCatalogFiles = files.filter((file) => file.relative.endsWith("libs.versions.toml"));
804
892
  const iosPbxFiles = pbxFiles.filter((file) => /(?:^|\/)ios\//.test(file.relative) || /\b(?:SDKROOT\s*=\s*iphoneos|IPHONEOS_DEPLOYMENT_TARGET|TARGETED_DEVICE_FAMILY)\b/.test(file.text));
805
893
  const iosPlistFiles = plistFiles.filter((file) => /(?:^|\/)ios\//.test(file.relative) || iosPbxFiles.length > 0);
806
- const androidPackageNames = [...expo.androidPackageNames];
894
+ const unitySettingsFiles = files.filter((file) => /(?:^|\/)ProjectSettings\/ProjectSettings\.asset$/.test(file.relative));
895
+ const unityAndroidPackageNames = [];
896
+ const unityIosBundleIds = [];
897
+ const unityTargetSdkEvidence = [];
898
+ for (const file of unitySettingsFiles) {
899
+ const identifiers = unityApplicationIdentifiers(file.text);
900
+ const androidId = identifiers.android;
901
+ const iosId = identifiers.ios;
902
+ const targetSdk = file.text.match(/^\s*AndroidTargetSdkVersion:\s*(-?\d+)\s*$/m)?.[1];
903
+ if (androidId) unityAndroidPackageNames.push(androidId);
904
+ if (iosId) unityIosBundleIds.push(iosId);
905
+ if (targetSdk && Number.parseInt(targetSdk, 10) > 0) {
906
+ unityTargetSdkEvidence.push({ file: file.relative, value: Number.parseInt(targetSdk, 10) });
907
+ }
908
+ }
909
+ const androidPackageNames = [...expo.androidPackageNames, ...unityAndroidPackageNames];
807
910
  for (const file of androidAppGradleFiles) {
808
911
  for (const match of file.text.matchAll(/\bapplicationId\s*(?:=\s*)?["']([^"']+)["']/g)) {
809
912
  androidPackageNames.push(match[1]);
810
913
  }
811
914
  }
812
- const iosBundleIds = [...expo.iosBundleIds];
915
+ const iosBundleIds = [...expo.iosBundleIds, ...unityIosBundleIds];
813
916
  for (const file of iosPbxFiles) {
814
917
  for (const match of file.text.matchAll(/PRODUCT_BUNDLE_IDENTIFIER\s*=\s*([^;]+);/g)) {
815
918
  const value = match[1].trim().replace(/^["']|["']$/g, "");
@@ -824,15 +927,16 @@ function detectProject(files) {
824
927
  }
825
928
  const expoTargetsAndroid = expo.detected && (expo.platforms.size === 0 || expo.platforms.has("android"));
826
929
  const expoTargetsIos = expo.detected && (expo.platforms.size === 0 || expo.platforms.has("ios"));
827
- const android = androidAppGradleFiles.length > 0 || expo.androidPackageNames.length > 0 || expoTargetsAndroid || androidAppManifestFiles.length > 0;
828
- const ios = iosPbxFiles.length > 0 || iosPlistFiles.some((file) => /(?:^|\/)ios\//.test(file.relative)) || expo.iosBundleIds.length > 0 || expoTargetsIos;
930
+ const android = androidAppGradleFiles.length > 0 || expo.androidPackageNames.length > 0 || expoTargetsAndroid || androidAppManifestFiles.length > 0 || unityAndroidPackageNames.length > 0 || unityTargetSdkEvidence.length > 0;
931
+ const ios = iosPbxFiles.length > 0 || iosPlistFiles.some((file) => /(?:^|\/)ios\//.test(file.relative)) || expo.iosBundleIds.length > 0 || expoTargetsIos || unityIosBundleIds.length > 0;
829
932
  const androidGradleFiles = gradleFiles.filter((file) => androidAppGradleFiles.includes(file) || /(?:^|\/)android\//.test(file.relative));
830
933
  return {
831
934
  android,
832
935
  ios,
833
936
  androidPackageNames: unique(androidPackageNames),
834
937
  iosBundleIds: unique(iosBundleIds),
835
- gradleFiles: [...androidGradleFiles, ...versionCatalogFiles]
938
+ gradleFiles: [...androidGradleFiles, ...versionCatalogFiles],
939
+ targetSdkEvidence: unityTargetSdkEvidence
836
940
  };
837
941
  }
838
942
  function targetPolicy(now) {
@@ -845,10 +949,11 @@ function targetPolicy(now) {
845
949
  effectiveDate: current?.effectiveDate
846
950
  };
847
951
  }
848
- function targetSdkFindings(gradleFiles, now) {
849
- const evidence = [];
952
+ function targetSdkFindings(gradleFiles, now, supplementalEvidence = []) {
953
+ const evidence = [...supplementalEvidence];
850
954
  let hasUnresolvedExpression = false;
851
955
  const catalogs = /* @__PURE__ */ new Map();
956
+ let reactNativeTargetSdk;
852
957
  for (const file of gradleFiles.filter((candidate) => candidate.relative.endsWith("libs.versions.toml"))) {
853
958
  let section2 = "";
854
959
  for (const rawLine of file.text.split(/\r?\n/)) {
@@ -860,21 +965,36 @@ function targetSdkFindings(gradleFiles, now) {
860
965
  }
861
966
  if (section2 !== "versions") continue;
862
967
  const version = line.match(/^([A-Za-z0-9_.-]+)\s*=\s*["'](\d+)["']/);
863
- if (version) catalogs.set(version[1], { value: Number.parseInt(version[2], 10), file: file.relative });
968
+ if (version) {
969
+ const parsed = { value: Number.parseInt(version[2], 10), file: file.relative };
970
+ if (file.relative === "node_modules/react-native/gradle/libs.versions.toml" && version[1] === "targetSdk") {
971
+ reactNativeTargetSdk = parsed;
972
+ } else {
973
+ catalogs.set(version[1], parsed);
974
+ }
975
+ }
864
976
  }
865
977
  }
866
978
  for (const file of gradleFiles.filter((candidate) => /build\.gradle(?:\.kts)?$/.test(candidate.relative))) {
979
+ let resolvedIndirectly = false;
867
980
  for (const match of file.text.matchAll(/\btargetSdk(?:Version)?\s*(?:=\s*)?(\d+)/g)) {
868
981
  evidence.push({ file: file.relative, value: Number.parseInt(match[1], 10) });
869
982
  }
870
983
  for (const match of file.text.matchAll(/\btargetSdk(?:Version)?\s*(?:=\s*)?libs\.versions\.([A-Za-z0-9_.-]+?)(?=\.get\(\)|\s|$)/g)) {
871
984
  const resolved = catalogs.get(match[1]) ?? catalogs.get(match[1].replace(/\./g, "-"));
872
- if (resolved) evidence.push({ file: resolved.file, value: resolved.value });
985
+ if (resolved) {
986
+ evidence.push({ file: resolved.file, value: resolved.value });
987
+ resolvedIndirectly = true;
988
+ }
989
+ }
990
+ if (/\btargetSdkVersion\s+rootProject\.ext\.targetSdkVersion\b/.test(file.text) && reactNativeTargetSdk) {
991
+ evidence.push({ file: reactNativeTargetSdk.file, value: reactNativeTargetSdk.value });
992
+ resolvedIndirectly = true;
873
993
  }
874
994
  if (/\btargetSdk(?:Version)?\b/.test(file.text) && !/\btargetSdk(?:Version)?\s*(?:=\s*)?\d+/.test(file.text)) {
875
995
  const catalogExpression = /\btargetSdk(?:Version)?\s*(?:=\s*)?libs\.versions\.([A-Za-z0-9_.-]+?)(?=\.get\(\)|\s|$)/.exec(file.text);
876
996
  const resolved = catalogExpression ? catalogs.get(catalogExpression[1]) ?? catalogs.get(catalogExpression[1].replace(/\./g, "-")) : void 0;
877
- if (!resolved) hasUnresolvedExpression = true;
997
+ if (!resolved && !resolvedIndirectly) hasUnresolvedExpression = true;
878
998
  }
879
999
  }
880
1000
  const policy = targetPolicy(now);
@@ -956,7 +1076,16 @@ async function scanReleaseDoctor(projectPath, now = /* @__PURE__ */ new Date())
956
1076
  }
957
1077
  if (!stat.isDirectory()) throw new Error(`Project path is not a directory: ${root}`);
958
1078
  const files = await walk2(root);
959
- const detected = detectProject(files);
1079
+ const reactNativeCatalog = path2.join(root, "node_modules", "react-native", "gradle", "libs.versions.toml");
1080
+ try {
1081
+ files.push({
1082
+ absolute: reactNativeCatalog,
1083
+ relative: "node_modules/react-native/gradle/libs.versions.toml",
1084
+ text: await fs2.readFile(reactNativeCatalog, "utf8")
1085
+ });
1086
+ } catch {
1087
+ }
1088
+ const detected = await detectProject(files, root);
960
1089
  const platforms = [];
961
1090
  if (detected.android) platforms.push("android");
962
1091
  if (detected.ios) platforms.push("ios");
@@ -1030,7 +1159,7 @@ async function scanReleaseDoctor(projectPath, now = /* @__PURE__ */ new Date())
1030
1159
  }
1031
1160
  });
1032
1161
  } else {
1033
- findings.push(...targetSdkFindings(detected.gradleFiles, now));
1162
+ findings.push(...targetSdkFindings(detected.gradleFiles, now, detected.targetSdkEvidence));
1034
1163
  }
1035
1164
  const billing = await checkBillingCompliance(root, now);
1036
1165
  if (billing.status !== "not_used") {
@@ -1988,7 +2117,7 @@ async function cmdAuth(args) {
1988
2117
  if (sub === "threads") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["threads", ...rest]));
1989
2118
  if (sub === "tiktok") return void exitWith(await runMcpBin("mimi-seed-tiktok-business-auth", rest));
1990
2119
  if (sub === "ci") {
1991
- const { cmdSetup: cmdSetup2 } = await import("./setup-RI2NDOZR.js");
2120
+ const { cmdSetup: cmdSetup2 } = await import("./setup-VO5WN32U.js");
1992
2121
  await cmdSetup2(["--only", "github,gitlab", "--reconnect", "github,gitlab"]);
1993
2122
  return;
1994
2123
  }
@@ -2134,7 +2263,7 @@ function readJson(filePath) {
2134
2263
  }
2135
2264
  var BUILTIN_MIMI_SEED_SERVER = {
2136
2265
  command: "npx",
2137
- args: ["-y", "@yoonion/mimi-seed-mcp"]
2266
+ args: ["-y", "@yoonion/mimi-seed-mcp@latest"]
2138
2267
  };
2139
2268
  function detectMcpClient(env = process.env) {
2140
2269
  if (env.CODEX_THREAD_ID || env.CODEX_CI || env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE) return "codex";
@@ -2188,7 +2317,8 @@ function candidateMarkers(cfg) {
2188
2317
  const primary = findProcessMarker(cfg);
2189
2318
  if (!primary) return [];
2190
2319
  const base = primary.split("/").pop();
2191
- return base && base !== primary ? [primary, base] : [primary];
2320
+ const executableBase = base?.replace(/@(?:latest|\d+(?:\.\d+){1,3}(?:[-+][\w.-]+)?)$/, "");
2321
+ return [...new Set([primary, base, executableBase].filter((value) => Boolean(value)))];
2192
2322
  }
2193
2323
  function findPids(markers) {
2194
2324
  let out;
@@ -2463,7 +2593,7 @@ var M8 = catalog(
2463
2593
  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+).",
2464
2594
  localStep1: "1) Google \uB85C\uADF8\uC778 (Firebase / AdMob / Play / Ads):",
2465
2595
  localStep2: "2) \uB85C\uCEEC MCP \uC11C\uBC84 \uB4F1\uB85D (\uC6D0\uACA9 'mimi-seed' \uC640 \uBCC4\uAC1C):",
2466
- localCodexHint: ' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"]',
2596
+ localCodexHint: ' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp@latest"]',
2467
2597
  localStep3: "3) \uB098\uBA38\uC9C0 \uACC4\uC815 \uC5F0\uACB0 (App Store / Play / Jenkins / CI / \uC18C\uC15C \u2026):",
2468
2598
  localSetupHint: " \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 \uD1A0\uD070 \uBC1C\uAE09 \uBC29\uBC95\uC744 \uC54C\uB824\uC90D\uB2C8\uB2E4.",
2469
2599
  notConnected: "\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589.",
@@ -2633,7 +2763,7 @@ ${kleur9.bold("\uD658\uACBD\uBCC0\uC218:")}
2633
2763
  localIntro: "The remote MCP (PAT, read + diagnostics) is done above. The local MCP runs every store write tool directly via Google OAuth (Node 20+).",
2634
2764
  localStep1: "1) Sign in with Google (Firebase / AdMob / Play / Ads):",
2635
2765
  localStep2: "2) Register the local MCP server (separate from the remote 'mimi-seed'):",
2636
- localCodexHint: ' Codex: add [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"] to ~/.codex/config.toml',
2766
+ localCodexHint: ' Codex: add [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp@latest"] to ~/.codex/config.toml',
2637
2767
  localStep3: "3) Connect the remaining accounts (App Store / Play / Jenkins / CI / social \u2026):",
2638
2768
  localSetupHint: " Press [?] on any item to see how to obtain that token.",
2639
2769
  notConnected: "No Mimi Seed account connected. Run `mimi-seed init`.",
@@ -2933,7 +3063,7 @@ async function cmdInit(args) {
2933
3063
  await cmdAuth(["login"]);
2934
3064
  log4("");
2935
3065
  log4(M8().localStep2);
2936
- log4(kleur9.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp"));
3066
+ log4(kleur9.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp@latest"));
2937
3067
  log4(kleur9.dim(M8().localCodexHint));
2938
3068
  log4("");
2939
3069
  log4(M8().localStep3);
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  mcpCall
4
- } from "./chunk-I3C5DIUA.js";
5
- import "./chunk-ZGMPOJRY.js";
4
+ } from "./chunk-2FKWHED2.js";
5
+ import "./chunk-TQHLWSFR.js";
6
6
  export {
7
7
  mcpCall
8
8
  };
@@ -3,8 +3,8 @@ import {
3
3
  cmdSetup,
4
4
  parseSetupArgs,
5
5
  resolveMode
6
- } from "./chunk-KY6LI3CK.js";
7
- import "./chunk-ZGMPOJRY.js";
6
+ } from "./chunk-UKNCSE4V.js";
7
+ import "./chunk-TQHLWSFR.js";
8
8
  export {
9
9
  cmdSetup,
10
10
  parseSetupArgs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mimi-seed",
3
- "version": "0.19.10",
3
+ "version": "0.19.11",
4
4
  "description": "Mimi Seed CLI \u2014 Claude Code\uc640 Codex\uc5d0\uc11c \uc571 \ucd9c\uc2dc \uc6b4\uc601\uc744 \uad00\ub9ac\ud569\ub2c8\ub2e4.",
5
5
  "bin": {
6
6
  "mimi-seed": "dist/index.js"