mimi-seed 0.20.0 → 0.21.1
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.
|
@@ -3,143 +3,95 @@
|
|
|
3
3
|
// src/setup.ts
|
|
4
4
|
import kleur2 from "kleur";
|
|
5
5
|
import * as readline2 from "readline";
|
|
6
|
-
import
|
|
6
|
+
import os8 from "os";
|
|
7
7
|
|
|
8
8
|
// src/credentials.ts
|
|
9
9
|
import fs3 from "fs";
|
|
10
10
|
import os2 from "os";
|
|
11
|
-
import
|
|
11
|
+
import path3 from "path";
|
|
12
12
|
|
|
13
|
-
// src/
|
|
13
|
+
// ../core/src/lang.ts
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import os from "os";
|
|
16
|
-
import path2 from "path";
|
|
17
|
-
|
|
18
|
-
// src/lib/atomic-write.ts
|
|
19
|
-
import { chmodSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
20
|
-
import { randomUUID } from "crypto";
|
|
21
16
|
import path from "path";
|
|
22
|
-
var CREDENTIAL_FILE_MODE = 384;
|
|
23
|
-
var CREDENTIAL_DIR_MODE = 448;
|
|
24
|
-
var RENAME_RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
25
|
-
var RENAME_RETRY_DELAYS_MS = [10, 20, 40, 80, 160, 320, 370];
|
|
26
|
-
function sleepSync(ms) {
|
|
27
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
28
|
-
}
|
|
29
|
-
function renameWithRetry(from, to, rename = renameSync, sleep = sleepSync) {
|
|
30
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
31
|
-
try {
|
|
32
|
-
rename(from, to);
|
|
33
|
-
return;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
const code = error.code;
|
|
36
|
-
if (!code || !RENAME_RETRY_CODES.has(code) || attempt >= RENAME_RETRY_DELAYS_MS.length) throw error;
|
|
37
|
-
sleep(RENAME_RETRY_DELAYS_MS[attempt]);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function writeFileAtomic(filePath, contents, options = {}) {
|
|
42
|
-
mkdirSync(path.dirname(filePath), { recursive: true, ...options.dirMode !== void 0 && { mode: options.dirMode } });
|
|
43
|
-
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
44
|
-
try {
|
|
45
|
-
writeFileSync(tempPath, contents, {
|
|
46
|
-
encoding: "utf8",
|
|
47
|
-
...options.mode !== void 0 && { mode: options.mode }
|
|
48
|
-
});
|
|
49
|
-
if (options.mode !== void 0 && process.platform !== "win32") chmodSync(tempPath, options.mode);
|
|
50
|
-
renameWithRetry(tempPath, filePath, options.rename, options.sleep);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
try {
|
|
53
|
-
unlinkSync(tempPath);
|
|
54
|
-
} catch {
|
|
55
|
-
}
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function writeJsonAtomic(filePath, value, options = {}) {
|
|
60
|
-
writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}
|
|
61
|
-
`, options);
|
|
62
|
-
}
|
|
63
|
-
function writeCredentialJson(filePath, value) {
|
|
64
|
-
writeJsonAtomic(filePath, value, { mode: CREDENTIAL_FILE_MODE, dirMode: CREDENTIAL_DIR_MODE });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/settings.ts
|
|
68
17
|
var DEFAULT_LANG = "ko";
|
|
69
|
-
function settingsPath(home) {
|
|
70
|
-
return path2.join(home, ".mimi-seed", "settings.json");
|
|
71
|
-
}
|
|
72
|
-
function readSettings(home = os.homedir()) {
|
|
73
|
-
try {
|
|
74
|
-
return JSON.parse(fs.readFileSync(settingsPath(home), "utf-8"));
|
|
75
|
-
} catch {
|
|
76
|
-
return {};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function writeSettings(next, home = os.homedir()) {
|
|
80
|
-
const merged = { ...readSettings(home), ...next };
|
|
81
|
-
writeJsonAtomic(settingsPath(home), merged, { dirMode: 448 });
|
|
82
|
-
}
|
|
83
|
-
function isLangUnset(home = os.homedir()) {
|
|
84
|
-
return !process.env.MIMI_SEED_LANG && !readSettings(home).lang;
|
|
85
|
-
}
|
|
86
18
|
function isLang(v) {
|
|
87
19
|
return v === "ko" || v === "en";
|
|
88
20
|
}
|
|
21
|
+
function settingsPath(home = os.homedir()) {
|
|
22
|
+
return path.join(home, ".mimi-seed", "settings.json");
|
|
23
|
+
}
|
|
89
24
|
function resolveLang(home = os.homedir()) {
|
|
90
25
|
const env = process.env.MIMI_SEED_LANG?.toLowerCase();
|
|
91
26
|
if (isLang(env)) return env;
|
|
92
|
-
|
|
93
|
-
|
|
27
|
+
try {
|
|
28
|
+
const saved = JSON.parse(fs.readFileSync(settingsPath(home), "utf-8"))?.lang;
|
|
29
|
+
if (isLang(saved)) return saved;
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
94
32
|
return DEFAULT_LANG;
|
|
95
33
|
}
|
|
96
34
|
|
|
97
|
-
// src/project-manifest.ts
|
|
35
|
+
// ../core/src/project-manifest.ts
|
|
98
36
|
import fs2 from "fs";
|
|
99
|
-
import
|
|
37
|
+
import path2 from "path";
|
|
100
38
|
var MANIFEST_FILENAME = ".mimi-seed.json";
|
|
101
|
-
|
|
102
|
-
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(value);
|
|
103
|
-
}
|
|
104
|
-
function manifestSocialProfile(m, platform) {
|
|
105
|
-
const profiles = m.socialProfiles;
|
|
106
|
-
if (profiles === void 0) return null;
|
|
107
|
-
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) {
|
|
108
|
-
throw new Error(`${MANIFEST_FILENAME} socialProfiles must be an object`);
|
|
109
|
-
}
|
|
110
|
-
const value = profiles[platform];
|
|
111
|
-
if (value === void 0) return null;
|
|
112
|
-
if (typeof value !== "string" || !isValidSocialProfileId(value)) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`${MANIFEST_FILENAME} socialProfiles.${platform} must be a safe 1-64 character profile id`
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
return value;
|
|
118
|
-
}
|
|
39
|
+
var SOCIAL_PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
119
40
|
function findProjectManifest(startDir = process.cwd(), maxDepth = 8) {
|
|
120
|
-
let dir =
|
|
41
|
+
let dir = path2.resolve(startDir);
|
|
121
42
|
for (let i = 0; i <= maxDepth; i++) {
|
|
122
|
-
const candidate =
|
|
43
|
+
const candidate = path2.join(dir, MANIFEST_FILENAME);
|
|
123
44
|
if (fs2.existsSync(candidate)) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (obj && typeof obj === "object") return { manifest: obj, filePath: candidate };
|
|
127
|
-
} catch {
|
|
128
|
-
}
|
|
45
|
+
const parsed = safeParse(candidate);
|
|
46
|
+
if (parsed) return { manifest: parsed, filePath: candidate };
|
|
129
47
|
return null;
|
|
130
48
|
}
|
|
131
|
-
const parent =
|
|
49
|
+
const parent = path2.dirname(dir);
|
|
132
50
|
if (parent === dir) break;
|
|
133
51
|
dir = parent;
|
|
134
52
|
}
|
|
135
53
|
return null;
|
|
136
54
|
}
|
|
55
|
+
function safeParse(filePath) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
58
|
+
const obj = JSON.parse(raw);
|
|
59
|
+
if (obj && typeof obj === "object") return obj;
|
|
60
|
+
return null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
137
65
|
function manifestServiceEntries(m) {
|
|
138
66
|
const svc = m.services ?? {};
|
|
139
67
|
return Object.keys(svc).filter((k) => svc[k] != null).map((k) => [k, svc[k]]);
|
|
140
68
|
}
|
|
69
|
+
function isValidSocialProfileId(value) {
|
|
70
|
+
return SOCIAL_PROFILE_ID_PATTERN.test(value);
|
|
71
|
+
}
|
|
72
|
+
var SOCIAL_PROFILE_MESSAGES_KO = {
|
|
73
|
+
notAnObject: `${MANIFEST_FILENAME}\uC758 socialProfiles\uB294 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`,
|
|
74
|
+
invalidId: (platform) => `${MANIFEST_FILENAME}\uC758 socialProfiles.${platform} \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC601\uBB38\uC790\xB7\uC22B\uC790\uB85C \uC2DC\uC791\uD558\uB294 1~64\uC790\uC758 \uC601\uBB38\uC790/\uC22B\uC790/\uC810/\uBC11\uC904/\uD558\uC774\uD508\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
|
|
75
|
+
};
|
|
76
|
+
function manifestSocialProfile(m, platform, messages = SOCIAL_PROFILE_MESSAGES_KO) {
|
|
77
|
+
const profiles = m.socialProfiles;
|
|
78
|
+
if (profiles === void 0) return null;
|
|
79
|
+
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) {
|
|
80
|
+
throw new Error(messages.notAnObject);
|
|
81
|
+
}
|
|
82
|
+
const value = profiles[platform];
|
|
83
|
+
if (value === void 0) return null;
|
|
84
|
+
if (typeof value !== "string" || !isValidSocialProfileId(value)) {
|
|
85
|
+
throw new Error(messages.invalidId(platform));
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
141
89
|
|
|
142
90
|
// src/credentials.ts
|
|
91
|
+
var SOCIAL_PROFILE_MESSAGES = {
|
|
92
|
+
notAnObject: `${MANIFEST_FILENAME} socialProfiles must be an object`,
|
|
93
|
+
invalidId: (platform) => `${MANIFEST_FILENAME} socialProfiles.${platform} must be a safe 1-64 character profile id`
|
|
94
|
+
};
|
|
143
95
|
function credLabel(spec, lang = resolveLang()) {
|
|
144
96
|
return spec.label[lang];
|
|
145
97
|
}
|
|
@@ -150,10 +102,10 @@ function credObtain(spec, lang = resolveLang()) {
|
|
|
150
102
|
return spec.obtain[lang];
|
|
151
103
|
}
|
|
152
104
|
function credDir(home) {
|
|
153
|
-
return
|
|
105
|
+
return path3.join(home, ".mimi-seed");
|
|
154
106
|
}
|
|
155
107
|
function hasFile(home, name) {
|
|
156
|
-
return fs3.existsSync(
|
|
108
|
+
return fs3.existsSync(path3.join(credDir(home), name));
|
|
157
109
|
}
|
|
158
110
|
function anyFileStarting(home, prefix) {
|
|
159
111
|
try {
|
|
@@ -164,7 +116,7 @@ function anyFileStarting(home, prefix) {
|
|
|
164
116
|
}
|
|
165
117
|
function readJson(home, name) {
|
|
166
118
|
try {
|
|
167
|
-
return JSON.parse(fs3.readFileSync(
|
|
119
|
+
return JSON.parse(fs3.readFileSync(path3.join(credDir(home), name), "utf-8"));
|
|
168
120
|
} catch {
|
|
169
121
|
return null;
|
|
170
122
|
}
|
|
@@ -199,14 +151,14 @@ function detectProjectSocialToken(home, startDir, platform) {
|
|
|
199
151
|
if (!loaded) return detectSocialToken(home, `${platform}.json`, idKey, tokenKey);
|
|
200
152
|
let profile;
|
|
201
153
|
try {
|
|
202
|
-
profile = manifestSocialProfile(loaded.manifest, platform);
|
|
154
|
+
profile = manifestSocialProfile(loaded.manifest, platform, SOCIAL_PROFILE_MESSAGES);
|
|
203
155
|
} catch (error) {
|
|
204
156
|
return { present: false, detail: error instanceof Error ? error.message : String(error) };
|
|
205
157
|
}
|
|
206
158
|
if (!profile) return detectSocialToken(home, `${platform}.json`, idKey, tokenKey);
|
|
207
159
|
const detected = detectSocialToken(
|
|
208
160
|
home,
|
|
209
|
-
|
|
161
|
+
path3.join("social-profiles", `${profile}.json`),
|
|
210
162
|
idKey,
|
|
211
163
|
tokenKey,
|
|
212
164
|
platform
|
|
@@ -219,7 +171,7 @@ function detectProjectSocialToken(home, startDir, platform) {
|
|
|
219
171
|
function hasPlaySa(home) {
|
|
220
172
|
if (hasFile(home, "play-service-account.json")) return true;
|
|
221
173
|
try {
|
|
222
|
-
return fs3.readdirSync(
|
|
174
|
+
return fs3.readdirSync(path3.join(credDir(home), "play-service-accounts")).some((f) => f.endsWith(".json"));
|
|
223
175
|
} catch {
|
|
224
176
|
return false;
|
|
225
177
|
}
|
|
@@ -716,7 +668,7 @@ function planSetup(detected, opts = {}) {
|
|
|
716
668
|
// src/mcp-bin.ts
|
|
717
669
|
import { spawn, spawnSync } from "child_process";
|
|
718
670
|
import { existsSync, readFileSync } from "fs";
|
|
719
|
-
import
|
|
671
|
+
import path4 from "path";
|
|
720
672
|
import { fileURLToPath } from "url";
|
|
721
673
|
|
|
722
674
|
// src/i18n.ts
|
|
@@ -941,7 +893,7 @@ function t() {
|
|
|
941
893
|
}
|
|
942
894
|
|
|
943
895
|
// package.json
|
|
944
|
-
var version = "0.
|
|
896
|
+
var version = "0.21.1";
|
|
945
897
|
|
|
946
898
|
// src/mcp-bin.ts
|
|
947
899
|
var MCP_PKG = "@yoonion/mimi-seed-mcp";
|
|
@@ -949,11 +901,11 @@ function resolveOnPath(bin, honorForceNpx = true) {
|
|
|
949
901
|
if (honorForceNpx && process.env.MIMI_SEED_FORCE_NPX) return null;
|
|
950
902
|
if (process.platform === "win32") {
|
|
951
903
|
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path");
|
|
952
|
-
const directories = (pathKey ? process.env[pathKey] ?? "" : "").split(
|
|
904
|
+
const directories = (pathKey ? process.env[pathKey] ?? "" : "").split(path4.delimiter).filter(Boolean);
|
|
953
905
|
const names = bin.toLowerCase().endsWith(".cmd") ? [bin] : [`${bin}.cmd`, bin];
|
|
954
906
|
for (const directory of directories) {
|
|
955
907
|
for (const name of names) {
|
|
956
|
-
const candidate =
|
|
908
|
+
const candidate = path4.join(directory.replace(/^"|"$/g, ""), name);
|
|
957
909
|
if (existsSync(candidate)) return candidate;
|
|
958
910
|
}
|
|
959
911
|
}
|
|
@@ -969,13 +921,13 @@ function resolveWindowsShimTarget(shimPath, source) {
|
|
|
969
921
|
const matches = [...source.matchAll(/["']([^"']+\.js)["']\s+%\*/gi)];
|
|
970
922
|
const raw = matches.at(-1)?.[1];
|
|
971
923
|
if (!raw) return null;
|
|
972
|
-
return
|
|
924
|
+
return path4.win32.normalize(raw.replace(/%~?dp0%?/gi, `${path4.win32.dirname(shimPath)}${path4.win32.sep}`));
|
|
973
925
|
}
|
|
974
926
|
function npxCliPath(shimPath) {
|
|
975
927
|
const candidates = [
|
|
976
|
-
shimPath ?
|
|
977
|
-
|
|
978
|
-
process.env.npm_execpath ?
|
|
928
|
+
shimPath ? path4.join(path4.dirname(shimPath), "node_modules", "npm", "bin", "npx-cli.js") : "",
|
|
929
|
+
path4.join(path4.dirname(process.execPath), "node_modules", "npm", "bin", "npx-cli.js"),
|
|
930
|
+
process.env.npm_execpath ? path4.join(path4.dirname(process.env.npm_execpath), "npx-cli.js") : ""
|
|
979
931
|
];
|
|
980
932
|
return candidates.find((candidate) => candidate && existsSync(candidate)) ?? null;
|
|
981
933
|
}
|
|
@@ -1046,6 +998,59 @@ async function runMcpBin(bin, extraArgs = []) {
|
|
|
1046
998
|
import fs4 from "fs";
|
|
1047
999
|
import os3 from "os";
|
|
1048
1000
|
import path6 from "path";
|
|
1001
|
+
|
|
1002
|
+
// ../core/src/atomic-write.ts
|
|
1003
|
+
import { chmodSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
1004
|
+
import { randomUUID } from "crypto";
|
|
1005
|
+
import path5 from "path";
|
|
1006
|
+
var CREDENTIAL_FILE_MODE = 384;
|
|
1007
|
+
var CREDENTIAL_DIR_MODE = 448;
|
|
1008
|
+
var RENAME_RETRY_DELAYS_MS = [10, 20, 40, 80, 160, 320, 370];
|
|
1009
|
+
var RENAME_RETRY_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
1010
|
+
function sleepSync(ms) {
|
|
1011
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1012
|
+
}
|
|
1013
|
+
function renameWithRetry(from, to, deps = {}) {
|
|
1014
|
+
const rename = deps.rename ?? renameSync;
|
|
1015
|
+
const sleep = deps.sleep ?? sleepSync;
|
|
1016
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1017
|
+
try {
|
|
1018
|
+
rename(from, to);
|
|
1019
|
+
return;
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
const code = error.code ?? "";
|
|
1022
|
+
if (!RENAME_RETRY_CODES.has(code) || attempt >= RENAME_RETRY_DELAYS_MS.length) throw error;
|
|
1023
|
+
sleep(RENAME_RETRY_DELAYS_MS[attempt]);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function writeFileAtomic(filePath, contents, options = {}) {
|
|
1028
|
+
mkdirSync(path5.dirname(filePath), { recursive: true, ...options.dirMode !== void 0 && { mode: options.dirMode } });
|
|
1029
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1030
|
+
try {
|
|
1031
|
+
writeFileSync(tempPath, contents, {
|
|
1032
|
+
encoding: "utf8",
|
|
1033
|
+
...options.mode !== void 0 && { mode: options.mode }
|
|
1034
|
+
});
|
|
1035
|
+
if (options.mode !== void 0 && process.platform !== "win32") chmodSync(tempPath, options.mode);
|
|
1036
|
+
renameWithRetry(tempPath, filePath, options);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
try {
|
|
1039
|
+
unlinkSync(tempPath);
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function writeJsonAtomic(filePath, value, options = {}) {
|
|
1046
|
+
writeFileAtomic(filePath, `${JSON.stringify(value, null, 2)}
|
|
1047
|
+
`, options);
|
|
1048
|
+
}
|
|
1049
|
+
function writeCredentialJson(filePath, value) {
|
|
1050
|
+
writeJsonAtomic(filePath, value, { mode: CREDENTIAL_FILE_MODE, dirMode: CREDENTIAL_DIR_MODE });
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/jenkins-config.ts
|
|
1049
1054
|
var CONFIG_DIR = path6.join(os3.homedir(), ".mimi-seed");
|
|
1050
1055
|
var JENKINS_PATH = path6.join(CONFIG_DIR, "jenkins.json");
|
|
1051
1056
|
var LEGACY_PATH = path6.join(CONFIG_DIR, "config.json");
|
|
@@ -1445,6 +1450,20 @@ async function consumeDeployStream(reader, onEvent) {
|
|
|
1445
1450
|
import fs7 from "fs/promises";
|
|
1446
1451
|
import path9 from "path";
|
|
1447
1452
|
|
|
1453
|
+
// ../core/src/http-errors.ts
|
|
1454
|
+
function endpointLabel(input, fallback) {
|
|
1455
|
+
try {
|
|
1456
|
+
const url = new URL(String(input));
|
|
1457
|
+
return `${url.host}${url.pathname}`;
|
|
1458
|
+
} catch {
|
|
1459
|
+
return fallback;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
function isTimeoutAbort(error) {
|
|
1463
|
+
const named = (e) => e?.name === "TimeoutError";
|
|
1464
|
+
return named(error) || named(error?.cause);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1448
1467
|
// src/lib/http.ts
|
|
1449
1468
|
var HTTP_TIMEOUT_MS = 3e4;
|
|
1450
1469
|
var HTTP_STREAM_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -1458,18 +1477,7 @@ var M2 = catalog(
|
|
|
1458
1477
|
unknownEndpoint: "the remote server"
|
|
1459
1478
|
}
|
|
1460
1479
|
);
|
|
1461
|
-
|
|
1462
|
-
try {
|
|
1463
|
-
const url = new URL(String(input));
|
|
1464
|
-
return `${url.host}${url.pathname}`;
|
|
1465
|
-
} catch {
|
|
1466
|
-
return M2().unknownEndpoint;
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
function isTimeoutAbort(error) {
|
|
1470
|
-
const named = (e) => e?.name === "TimeoutError";
|
|
1471
|
-
return named(error) || named(error?.cause);
|
|
1472
|
-
}
|
|
1480
|
+
var endpointLabel2 = (input) => endpointLabel(input, M2().unknownEndpoint);
|
|
1473
1481
|
function isBodyAbort(error) {
|
|
1474
1482
|
const named = (e) => {
|
|
1475
1483
|
const name = e?.name;
|
|
@@ -1478,7 +1486,7 @@ function isBodyAbort(error) {
|
|
|
1478
1486
|
return named(error) || named(error?.cause);
|
|
1479
1487
|
}
|
|
1480
1488
|
function timeoutError(input, timeoutMs, cause) {
|
|
1481
|
-
return new Error(M2().timedOut(
|
|
1489
|
+
return new Error(M2().timedOut(endpointLabel2(input), Math.max(1, Math.round(timeoutMs / 1e3))), { cause });
|
|
1482
1490
|
}
|
|
1483
1491
|
async function readBodyWithTimeout(input, timeoutMs, read) {
|
|
1484
1492
|
try {
|
|
@@ -1700,6 +1708,28 @@ function jenkinsBuildParameters(platform, ref, appId) {
|
|
|
1700
1708
|
import fs9 from "fs";
|
|
1701
1709
|
import path11 from "path";
|
|
1702
1710
|
import os5 from "os";
|
|
1711
|
+
|
|
1712
|
+
// ../core/src/ci.ts
|
|
1713
|
+
function githubApiBase(cfg) {
|
|
1714
|
+
if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
|
|
1715
|
+
return "https://api.github.com";
|
|
1716
|
+
}
|
|
1717
|
+
function githubHeaders(token) {
|
|
1718
|
+
return {
|
|
1719
|
+
Authorization: `Bearer ${token}`,
|
|
1720
|
+
Accept: "application/vnd.github+json",
|
|
1721
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1722
|
+
"Content-Type": "application/json"
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function gitlabApiBase(cfg) {
|
|
1726
|
+
return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
|
|
1727
|
+
}
|
|
1728
|
+
function gitlabProjectId(cfg) {
|
|
1729
|
+
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/ci-providers.ts
|
|
1703
1733
|
var CI_CONFIG_PATH = path11.join(os5.homedir(), ".mimi-seed", "ci.json");
|
|
1704
1734
|
var M6 = catalog(
|
|
1705
1735
|
{
|
|
@@ -1740,7 +1770,7 @@ async function verifyCiToken(cfg) {
|
|
|
1740
1770
|
const probe = { ...cfg, host: normalizeHost(cfg.host) };
|
|
1741
1771
|
try {
|
|
1742
1772
|
if (probe.provider === "github") {
|
|
1743
|
-
const res2 = await fetchWithTimeout(`${
|
|
1773
|
+
const res2 = await fetchWithTimeout(`${githubApiBase(probe)}/user`, {
|
|
1744
1774
|
headers: { Authorization: `Bearer ${probe.token}`, Accept: "application/vnd.github+json" }
|
|
1745
1775
|
});
|
|
1746
1776
|
if (!res2.ok) {
|
|
@@ -1753,7 +1783,7 @@ async function verifyCiToken(cfg) {
|
|
|
1753
1783
|
}
|
|
1754
1784
|
return { ok: true, login: user2.login };
|
|
1755
1785
|
}
|
|
1756
|
-
const res = await fetchWithTimeout(`${
|
|
1786
|
+
const res = await fetchWithTimeout(`${gitlabApiBase(probe)}/user`, { headers: { "PRIVATE-TOKEN": probe.token } });
|
|
1757
1787
|
if (!res.ok) {
|
|
1758
1788
|
return { ok: false, reason: M6().badToken("GitLab", res.status) };
|
|
1759
1789
|
}
|
|
@@ -1763,26 +1793,14 @@ async function verifyCiToken(cfg) {
|
|
|
1763
1793
|
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
1764
1794
|
}
|
|
1765
1795
|
}
|
|
1766
|
-
function ghBase(cfg) {
|
|
1767
|
-
if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
|
|
1768
|
-
return "https://api.github.com";
|
|
1769
|
-
}
|
|
1770
|
-
function ghHeaders(token) {
|
|
1771
|
-
return {
|
|
1772
|
-
Authorization: `Bearer ${token}`,
|
|
1773
|
-
Accept: "application/vnd.github+json",
|
|
1774
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
1775
|
-
"Content-Type": "application/json"
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
1796
|
async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
|
|
1779
1797
|
const startTime = /* @__PURE__ */ new Date();
|
|
1780
1798
|
const wfId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
|
|
1781
1799
|
const dispatchRes = await fetchWithTimeout(
|
|
1782
|
-
`${
|
|
1800
|
+
`${githubApiBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/dispatches`,
|
|
1783
1801
|
{
|
|
1784
1802
|
method: "POST",
|
|
1785
|
-
headers:
|
|
1803
|
+
headers: githubHeaders(cfg.token),
|
|
1786
1804
|
body: JSON.stringify({ ref, inputs })
|
|
1787
1805
|
}
|
|
1788
1806
|
);
|
|
@@ -1791,8 +1809,8 @@ async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
|
|
|
1791
1809
|
}
|
|
1792
1810
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
1793
1811
|
const runsRes = await fetchWithTimeout(
|
|
1794
|
-
`${
|
|
1795
|
-
{ headers:
|
|
1812
|
+
`${githubApiBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/runs?per_page=5`,
|
|
1813
|
+
{ headers: githubHeaders(cfg.token) }
|
|
1796
1814
|
);
|
|
1797
1815
|
if (!runsRes.ok) return null;
|
|
1798
1816
|
const data = await runsRes.json();
|
|
@@ -1808,8 +1826,8 @@ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, interval
|
|
|
1808
1826
|
let res;
|
|
1809
1827
|
try {
|
|
1810
1828
|
res = await fetchWithTimeout(
|
|
1811
|
-
`${
|
|
1812
|
-
{ headers:
|
|
1829
|
+
`${githubApiBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/runs/${runId}`,
|
|
1830
|
+
{ headers: githubHeaders(cfg.token) }
|
|
1813
1831
|
);
|
|
1814
1832
|
} catch {
|
|
1815
1833
|
consecutiveErrors++;
|
|
@@ -1832,17 +1850,11 @@ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, interval
|
|
|
1832
1850
|
}
|
|
1833
1851
|
return "timeout";
|
|
1834
1852
|
}
|
|
1835
|
-
function glBase(cfg) {
|
|
1836
|
-
return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
|
|
1837
|
-
}
|
|
1838
|
-
function glProjectId(cfg) {
|
|
1839
|
-
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
1840
|
-
}
|
|
1841
1853
|
async function glTriggerPipeline(cfg, ref, variables = {}) {
|
|
1842
1854
|
const vars = Object.entries(variables).map(([key, value]) => ({ key, value }));
|
|
1843
1855
|
const body = { ref };
|
|
1844
1856
|
if (vars.length > 0) body.variables = vars;
|
|
1845
|
-
const res = await fetchWithTimeout(`${
|
|
1857
|
+
const res = await fetchWithTimeout(`${gitlabApiBase(cfg)}/projects/${gitlabProjectId(cfg)}/pipeline`, {
|
|
1846
1858
|
method: "POST",
|
|
1847
1859
|
headers: { "PRIVATE-TOKEN": cfg.token, "Content-Type": "application/json" },
|
|
1848
1860
|
body: JSON.stringify(body)
|
|
@@ -1859,7 +1871,7 @@ async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3
|
|
|
1859
1871
|
let res;
|
|
1860
1872
|
try {
|
|
1861
1873
|
res = await fetchWithTimeout(
|
|
1862
|
-
`${
|
|
1874
|
+
`${gitlabApiBase(cfg)}/projects/${gitlabProjectId(cfg)}/pipelines/${pipelineId}`,
|
|
1863
1875
|
{ headers: { "PRIVATE-TOKEN": cfg.token } }
|
|
1864
1876
|
);
|
|
1865
1877
|
} catch {
|
|
@@ -2532,9 +2544,27 @@ async function cmdDeploy(argv) {
|
|
|
2532
2544
|
log(kleur.bold(M7().done));
|
|
2533
2545
|
}
|
|
2534
2546
|
|
|
2535
|
-
// src/
|
|
2547
|
+
// src/settings.ts
|
|
2536
2548
|
import fs10 from "fs";
|
|
2537
2549
|
import os6 from "os";
|
|
2550
|
+
function readSettings(home = os6.homedir()) {
|
|
2551
|
+
try {
|
|
2552
|
+
return JSON.parse(fs10.readFileSync(settingsPath(home), "utf-8"));
|
|
2553
|
+
} catch {
|
|
2554
|
+
return {};
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function writeSettings(next, home = os6.homedir()) {
|
|
2558
|
+
const merged = { ...readSettings(home), ...next };
|
|
2559
|
+
writeJsonAtomic(settingsPath(home), merged, { dirMode: 448 });
|
|
2560
|
+
}
|
|
2561
|
+
function isLangUnset(home = os6.homedir()) {
|
|
2562
|
+
return !process.env.MIMI_SEED_LANG && !readSettings(home).lang;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/telemetry.ts
|
|
2566
|
+
import fs11 from "fs";
|
|
2567
|
+
import os7 from "os";
|
|
2538
2568
|
import path12 from "path";
|
|
2539
2569
|
import { createHmac, randomUUID as randomUUID3 } from "crypto";
|
|
2540
2570
|
var ENDPOINT = "https://mimi-seed.pryzm.gg/api/sdk-usage";
|
|
@@ -2552,10 +2582,10 @@ var M8 = catalog(
|
|
|
2552
2582
|
invalid: "Usage: mimi-seed telemetry on|off|status\n"
|
|
2553
2583
|
}
|
|
2554
2584
|
);
|
|
2555
|
-
var location = () => path12.join(
|
|
2585
|
+
var location = () => path12.join(os7.homedir(), ".mimi-seed", "telemetry.json");
|
|
2556
2586
|
function readConsent() {
|
|
2557
2587
|
try {
|
|
2558
|
-
return JSON.parse(
|
|
2588
|
+
return JSON.parse(fs11.readFileSync(location(), "utf8"));
|
|
2559
2589
|
} catch {
|
|
2560
2590
|
return { enabled: false };
|
|
2561
2591
|
}
|
|
@@ -2585,9 +2615,9 @@ function cmdTelemetry(argv) {
|
|
|
2585
2615
|
process.stdout.write(telemetryEnabled() ? M8().enabled : M8().disabled);
|
|
2586
2616
|
}
|
|
2587
2617
|
function frameworkAt(root) {
|
|
2588
|
-
if (
|
|
2618
|
+
if (fs11.existsSync(path12.join(root, "ProjectSettings", "ProjectSettings.asset"))) return "unity";
|
|
2589
2619
|
try {
|
|
2590
|
-
const pkg = JSON.parse(
|
|
2620
|
+
const pkg = JSON.parse(fs11.readFileSync(path12.join(root, "package.json"), "utf8"));
|
|
2591
2621
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2592
2622
|
if (deps.expo) return "expo";
|
|
2593
2623
|
if (deps["react-native"]) return "react-native";
|
|
@@ -2605,7 +2635,7 @@ function usageRun(command, projectPath) {
|
|
|
2605
2635
|
if (!consent.installationId || !consent.salt) writeConsent({ enabled: consent.enabled, installationId, salt });
|
|
2606
2636
|
let normalized = path12.resolve(projectPath);
|
|
2607
2637
|
try {
|
|
2608
|
-
normalized =
|
|
2638
|
+
normalized = fs11.realpathSync(normalized);
|
|
2609
2639
|
} catch {
|
|
2610
2640
|
}
|
|
2611
2641
|
if (process.platform === "win32") normalized = normalized.toLowerCase();
|
|
@@ -2778,7 +2808,7 @@ async function connectOne(spec) {
|
|
|
2778
2808
|
}
|
|
2779
2809
|
async function cmdSetup(argv) {
|
|
2780
2810
|
const opts = parseSetupArgs(argv);
|
|
2781
|
-
const home =
|
|
2811
|
+
const home = os8.homedir();
|
|
2782
2812
|
migrateLegacyJenkins(home);
|
|
2783
2813
|
if (resolveMode(opts, process.env, process.stdin.isTTY) === "interactive") {
|
|
2784
2814
|
await ensureLangChosen();
|
|
@@ -2889,7 +2919,6 @@ export {
|
|
|
2889
2919
|
deleteConfig,
|
|
2890
2920
|
CONFIG_LOCATION,
|
|
2891
2921
|
getEffectiveConfig,
|
|
2892
|
-
writeSettings,
|
|
2893
2922
|
isLang,
|
|
2894
2923
|
resolveLang,
|
|
2895
2924
|
catalog,
|
|
@@ -2913,6 +2942,7 @@ export {
|
|
|
2913
2942
|
findProjectLink,
|
|
2914
2943
|
linkProject,
|
|
2915
2944
|
cmdDeploy,
|
|
2945
|
+
writeSettings,
|
|
2916
2946
|
telemetryNotice,
|
|
2917
2947
|
cmdTelemetry,
|
|
2918
2948
|
usageRun,
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
version,
|
|
33
33
|
writeConfig,
|
|
34
34
|
writeSettings
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-GWBZKH65.js";
|
|
36
36
|
|
|
37
37
|
// src/index.ts
|
|
38
38
|
import kleur11 from "kleur";
|
|
@@ -283,7 +283,7 @@ async function cmdAuth(args) {
|
|
|
283
283
|
if (sub === "threads") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["threads", ...rest]));
|
|
284
284
|
if (sub === "tiktok") return void exitWith(await runMcpBin("mimi-seed-tiktok-business-auth", rest));
|
|
285
285
|
if (sub === "ci") {
|
|
286
|
-
const { cmdSetup: cmdSetup2 } = await import("./setup-
|
|
286
|
+
const { cmdSetup: cmdSetup2 } = await import("./setup-5XA5MVLT.js");
|
|
287
287
|
await cmdSetup2(["--only", "github,gitlab", "--reconnect", "github,gitlab"]);
|
|
288
288
|
return;
|
|
289
289
|
}
|
|
@@ -927,7 +927,7 @@ async function cmdDoctor(args = []) {
|
|
|
927
927
|
// src/check.ts
|
|
928
928
|
import kleur5 from "kleur";
|
|
929
929
|
|
|
930
|
-
// src/checks/release-doctor-render.ts
|
|
930
|
+
// ../core/src/checks/release-doctor-render.ts
|
|
931
931
|
var COPY = {
|
|
932
932
|
ko: {
|
|
933
933
|
title: "Mimi Seed Release Doctor \u2014 \uB85C\uADF8\uC778 \uC5C6\uB294 \uCD9C\uC2DC \uC810\uAC80",
|
|
@@ -990,11 +990,11 @@ function renderReleaseDoctor(report, lang) {
|
|
|
990
990
|
return lines.join("\n");
|
|
991
991
|
}
|
|
992
992
|
|
|
993
|
-
// src/checks/release-doctor.ts
|
|
993
|
+
// ../core/src/checks/release-doctor.ts
|
|
994
994
|
import fs5 from "fs/promises";
|
|
995
995
|
import path5 from "path";
|
|
996
996
|
|
|
997
|
-
// src/checks/billing.ts
|
|
997
|
+
// ../core/src/checks/billing.ts
|
|
998
998
|
import fs4 from "fs/promises";
|
|
999
999
|
import path4 from "path";
|
|
1000
1000
|
var BILLING_MODULE = /com\.android\.billingclient:billing(?:-ktx)?/;
|
|
@@ -1400,7 +1400,7 @@ async function checkBillingCompliance(projectPath, now = /* @__PURE__ */ new Dat
|
|
|
1400
1400
|
};
|
|
1401
1401
|
}
|
|
1402
1402
|
|
|
1403
|
-
// src/checks/release-doctor.ts
|
|
1403
|
+
// ../core/src/checks/release-doctor.ts
|
|
1404
1404
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
1405
1405
|
".git",
|
|
1406
1406
|
".gradle",
|
|
@@ -2753,8 +2753,24 @@ import { createInterface } from "readline/promises";
|
|
|
2753
2753
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2754
2754
|
import kleur6 from "kleur";
|
|
2755
2755
|
|
|
2756
|
-
// src/ai
|
|
2757
|
-
var
|
|
2756
|
+
// ../core/src/ai.ts
|
|
2757
|
+
var AI_MODEL = "claude-haiku-4-5-20251001";
|
|
2758
|
+
var SENTIMENT_KEYWORDS = [
|
|
2759
|
+
["bug_report", ["\uBC84\uADF8", "\uC624\uB958", "\uC548\uB428", "crash", "bug", "error", "broken"]],
|
|
2760
|
+
["feature_request", ["\uCD94\uAC00", "\uC6D0\uD574", "\uC788\uC73C\uBA74", "wish", "feature", "add", "would like"]],
|
|
2761
|
+
["negative", ["\uBCC4\uB85C", "\uC2E4\uB9DD", "\uC9DC\uC99D", "terrible", "worst", "awful"]],
|
|
2762
|
+
["positive", ["\uC88B\uC544", "\uCD5C\uACE0", "\uD6CC\uB96D", "great", "excellent", "love", "perfect"]]
|
|
2763
|
+
];
|
|
2764
|
+
function detectReviewSentiment(text) {
|
|
2765
|
+
const lower = text.toLowerCase();
|
|
2766
|
+
for (const [sentiment, keywords] of SENTIMENT_KEYWORDS) {
|
|
2767
|
+
if (keywords.some((w) => lower.includes(w))) return sentiment;
|
|
2768
|
+
}
|
|
2769
|
+
return "neutral";
|
|
2770
|
+
}
|
|
2771
|
+
var REVIEW_REPLY_MAX_TOKENS = 500;
|
|
2772
|
+
var RELEASE_NOTES_MAX_TOKENS = 2e3;
|
|
2773
|
+
var RELEASE_NOTE_TONES = ["concise", "detailed", "marketing"];
|
|
2758
2774
|
|
|
2759
2775
|
// src/notes.ts
|
|
2760
2776
|
var M7 = catalog(
|
|
@@ -2762,19 +2778,17 @@ var M7 = catalog(
|
|
|
2762
2778
|
// Claude 프롬프트
|
|
2763
2779
|
localeHint: (l) => `"${l}": "\uD574\uB2F9 \uC5B8\uC5B4\uB85C \uBC88\uC5ED\uB41C \uAC04\uACB0\uD55C \uBC84\uC804"`,
|
|
2764
2780
|
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.",
|
|
2765
|
-
|
|
2781
|
+
toneHints: {
|
|
2782
|
+
concise: "\uAC04\uACB0\uD55C \uBC84\uC804 (3\uC904 \uC774\uB0B4, \uBD88\uB9BF)",
|
|
2783
|
+
detailed: "\uC0C1\uC138 \uBC84\uC804 (5\uAC1C \uC774\uB0B4, \uBD88\uB9BF)",
|
|
2784
|
+
marketing: "\uB9C8\uCF00\uD305 \uBC84\uC804 (\uC5F4\uC815\uC801 \uD1A4)"
|
|
2785
|
+
},
|
|
2786
|
+
userPrompt: (toneCount, commitsText, skeleton) => `\uB2E4\uC74C \uCEE4\uBC0B \uB0B4\uC5ED\uC73C\uB85C \uB9B4\uB9AC\uC988 \uB178\uD2B8\uB97C ${toneCount}\uAC00\uC9C0 \uD1A4\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694:
|
|
2766
2787
|
|
|
2767
2788
|
${commitsText}
|
|
2768
2789
|
|
|
2769
2790
|
JSON:
|
|
2770
|
-
{
|
|
2771
|
-
"concise": "\uAC04\uACB0\uD55C \uBC84\uC804 (3\uC904 \uC774\uB0B4, \uBD88\uB9BF)",
|
|
2772
|
-
"detailed": "\uC0C1\uC138 \uBC84\uC804 (5\uAC1C \uC774\uB0B4, \uBD88\uB9BF)",
|
|
2773
|
-
"marketing": "\uB9C8\uCF00\uD305 \uBC84\uC804 (\uC5F4\uC815\uC801 \uD1A4)",
|
|
2774
|
-
"localized": {
|
|
2775
|
-
${localeList}
|
|
2776
|
-
}
|
|
2777
|
-
}`,
|
|
2791
|
+
${skeleton}`,
|
|
2778
2792
|
parseFailed: "AI \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328",
|
|
2779
2793
|
// 템플릿 폴백
|
|
2780
2794
|
marketingTemplate: (items) => `\uC0C8\uB85C\uC6B4 \uC5C5\uB370\uC774\uD2B8\uAC00 \uC900\uBE44\uB410\uC2B5\uB2C8\uB2E4!
|
|
@@ -2819,19 +2833,17 @@ ${items}
|
|
|
2819
2833
|
// Claude prompt
|
|
2820
2834
|
localeHint: (l) => `"${l}": "the concise version, translated into that language"`,
|
|
2821
2835
|
system: "You are an expert app store release-notes copywriter. You turn commit history into user-friendly language. Always respond with valid JSON only.",
|
|
2822
|
-
|
|
2836
|
+
toneHints: {
|
|
2837
|
+
concise: "concise version (3 bullets max)",
|
|
2838
|
+
detailed: "detailed version (5 bullets max)",
|
|
2839
|
+
marketing: "marketing version (enthusiastic tone)"
|
|
2840
|
+
},
|
|
2841
|
+
userPrompt: (toneCount, commitsText, skeleton) => `Write release notes in ${toneCount} tones from the following commit history:
|
|
2823
2842
|
|
|
2824
2843
|
${commitsText}
|
|
2825
2844
|
|
|
2826
2845
|
JSON:
|
|
2827
|
-
{
|
|
2828
|
-
"concise": "concise version (3 bullets max)",
|
|
2829
|
-
"detailed": "detailed version (5 bullets max)",
|
|
2830
|
-
"marketing": "marketing version (enthusiastic tone)",
|
|
2831
|
-
"localized": {
|
|
2832
|
-
${localeList}
|
|
2833
|
-
}
|
|
2834
|
-
}`,
|
|
2846
|
+
${skeleton}`,
|
|
2835
2847
|
parseFailed: "Failed to parse the AI response",
|
|
2836
2848
|
// Template fallback
|
|
2837
2849
|
marketingTemplate: (items) => `A new update is here!
|
|
@@ -2891,18 +2903,30 @@ async function promptUser(question) {
|
|
|
2891
2903
|
rl.close();
|
|
2892
2904
|
return answer.trim();
|
|
2893
2905
|
}
|
|
2906
|
+
function buildReleaseNotesPrompt(commitsText, locales) {
|
|
2907
|
+
const m = M7();
|
|
2908
|
+
const toneHints = m.toneHints;
|
|
2909
|
+
const localeList = locales.map((l) => m.localeHint(l)).join(",\n ");
|
|
2910
|
+
const skeleton = [
|
|
2911
|
+
"{",
|
|
2912
|
+
...RELEASE_NOTE_TONES.map((tone) => ` "${tone}": "${toneHints[tone]}",`),
|
|
2913
|
+
` "localized": {
|
|
2914
|
+
${localeList}
|
|
2915
|
+
}`,
|
|
2916
|
+
"}"
|
|
2917
|
+
].join("\n");
|
|
2918
|
+
return m.userPrompt(RELEASE_NOTE_TONES.length, commitsText, skeleton);
|
|
2919
|
+
}
|
|
2894
2920
|
async function generateWithClaude(commitsText, locales) {
|
|
2895
2921
|
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
2896
|
-
const localeList = locales.map((l) => M7().localeHint(l)).join(",\n ");
|
|
2897
2922
|
const response = await client.messages.create({
|
|
2898
|
-
model:
|
|
2899
|
-
// mcp-server 의 generateReleaseNotes 기본값과
|
|
2900
|
-
|
|
2901
|
-
max_tokens: 2e3,
|
|
2923
|
+
model: AI_MODEL,
|
|
2924
|
+
// mcp-server 의 generateReleaseNotes 기본값과 같은 상수 — 낮으면 다국어 JSON 이 한쪽 경로에서만 잘린다.
|
|
2925
|
+
max_tokens: RELEASE_NOTES_MAX_TOKENS,
|
|
2902
2926
|
system: M7().system,
|
|
2903
2927
|
messages: [{
|
|
2904
2928
|
role: "user",
|
|
2905
|
-
content:
|
|
2929
|
+
content: buildReleaseNotesPrompt(commitsText, locales)
|
|
2906
2930
|
}]
|
|
2907
2931
|
});
|
|
2908
2932
|
const text = response.content[0].type === "text" ? response.content[0].text : "";
|
|
@@ -3161,28 +3185,20 @@ async function promptUser2(question) {
|
|
|
3161
3185
|
rl.close();
|
|
3162
3186
|
return answer.trim();
|
|
3163
3187
|
}
|
|
3164
|
-
function detectSentiment(text) {
|
|
3165
|
-
const lower = text.toLowerCase();
|
|
3166
|
-
if (["\uBC84\uADF8", "\uC624\uB958", "\uC548\uB428", "crash", "bug", "error", "broken"].some((w) => lower.includes(w))) return "bug_report";
|
|
3167
|
-
if (["\uCD94\uAC00", "\uC6D0\uD574", "\uC788\uC73C\uBA74", "wish", "feature", "add", "would like"].some((w) => lower.includes(w))) return "feature_request";
|
|
3168
|
-
if (["\uBCC4\uB85C", "\uC2E4\uB9DD", "\uC9DC\uC99D", "terrible", "worst", "awful"].some((w) => lower.includes(w))) return "negative";
|
|
3169
|
-
if (["\uC88B\uC544", "\uCD5C\uACE0", "\uD6CC\uB96D", "great", "excellent", "love", "perfect"].some((w) => lower.includes(w))) return "positive";
|
|
3170
|
-
return "neutral";
|
|
3171
|
-
}
|
|
3172
3188
|
async function generateReply(opts) {
|
|
3173
3189
|
const client = new Anthropic2({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
3174
3190
|
const m = M8();
|
|
3175
3191
|
const toneGuides = m.toneGuides;
|
|
3176
3192
|
const sentimentPrompts = m.sentimentPrompts;
|
|
3177
3193
|
const localeNames = m.localeNames;
|
|
3178
|
-
const sentiment =
|
|
3194
|
+
const sentiment = detectReviewSentiment(opts.text);
|
|
3179
3195
|
const toneGuide = toneGuides[opts.tone] ?? toneGuides.friendly;
|
|
3180
3196
|
const sentimentPrompt = sentimentPrompts[sentiment] ?? sentimentPrompts.neutral;
|
|
3181
3197
|
const stars = opts.rating !== void 0 ? m.starsLabel(`${"\u2605".repeat(opts.rating)}${"\u2606".repeat(5 - opts.rating)}`, opts.rating) : "";
|
|
3182
3198
|
const langName = localeNames[opts.language] ?? opts.language;
|
|
3183
3199
|
const response = await client.messages.create({
|
|
3184
|
-
model:
|
|
3185
|
-
max_tokens:
|
|
3200
|
+
model: AI_MODEL,
|
|
3201
|
+
max_tokens: REVIEW_REPLY_MAX_TOKENS,
|
|
3186
3202
|
system: m.system(langName, toneGuide, opts.developerName ?? m.defaultDeveloper),
|
|
3187
3203
|
messages: [{
|
|
3188
3204
|
role: "user",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mimi-seed",
|
|
3
3
|
"keywords": ["release-doctor", "expo", "react-native", "unity", "google-play", "app-store", "release-readiness", "mcp", "cli"],
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.21.1",
|
|
5
5
|
"description": "Release Doctor: check Expo, React Native and Unity release risks before connecting app stores.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mimi-seed": "dist/index.js"
|
|
@@ -19,11 +19,9 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"prebuild": "node ../../scripts/sync-release-doctor.mjs --check",
|
|
23
22
|
"build": "tsup",
|
|
24
23
|
"dev": "tsx src/index.ts",
|
|
25
24
|
"typecheck": "tsc --noEmit",
|
|
26
|
-
"pretest": "node ../../scripts/sync-release-doctor.mjs --check",
|
|
27
25
|
"test": "npm run typecheck && npm run lint && vitest run",
|
|
28
26
|
"test:watch": "vitest",
|
|
29
27
|
"lint": "eslint .",
|