mimi-seed 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -4
- package/dist/{chunk-Q5YGYAFK.js → chunk-DW3LOLPJ.js} +10 -0
- package/dist/chunk-T757WVSG.js +1356 -0
- package/dist/index.js +263 -992
- package/dist/{mcp-client-26VCPY4L.js → mcp-client-SLIKR42C.js} +1 -1
- package/dist/setup-ENJIMGJF.js +11 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,170 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
mcpCall
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DW3LOLPJ.js";
|
|
5
|
+
import {
|
|
6
|
+
CONFIG_LOCATION,
|
|
7
|
+
CREDENTIALS,
|
|
8
|
+
MCP_PKG,
|
|
9
|
+
cmdDeploy,
|
|
10
|
+
cmdSetup,
|
|
11
|
+
deleteConfig,
|
|
12
|
+
detectAll,
|
|
13
|
+
detectHints,
|
|
14
|
+
getEffectiveConfig,
|
|
15
|
+
hasAnyProjectSignal,
|
|
16
|
+
isSatisfied,
|
|
17
|
+
migrateLegacyJenkins,
|
|
18
|
+
runMcpBin,
|
|
19
|
+
tryCredById,
|
|
20
|
+
writeConfig
|
|
21
|
+
} from "./chunk-T757WVSG.js";
|
|
5
22
|
|
|
6
23
|
// src/index.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
24
|
+
import os3 from "os";
|
|
25
|
+
import fs5 from "fs";
|
|
26
|
+
import path5 from "path";
|
|
27
|
+
import kleur8 from "kleur";
|
|
11
28
|
import open from "open";
|
|
12
29
|
|
|
13
|
-
// src/detect.ts
|
|
14
|
-
import fs from "fs/promises";
|
|
15
|
-
import path from "path";
|
|
16
|
-
async function readIfExists(p) {
|
|
17
|
-
try {
|
|
18
|
-
return await fs.readFile(p, "utf8");
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function pathExists(p) {
|
|
24
|
-
try {
|
|
25
|
-
await fs.access(p);
|
|
26
|
-
return true;
|
|
27
|
-
} catch {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async function walk(root, match, maxDepth = 5) {
|
|
32
|
-
const found = [];
|
|
33
|
-
const skipDirs = /* @__PURE__ */ new Set([
|
|
34
|
-
"node_modules",
|
|
35
|
-
".git",
|
|
36
|
-
"build",
|
|
37
|
-
"dist",
|
|
38
|
-
".next",
|
|
39
|
-
".expo",
|
|
40
|
-
"Pods",
|
|
41
|
-
"DerivedData"
|
|
42
|
-
]);
|
|
43
|
-
async function visit(dir, depth) {
|
|
44
|
-
if (depth > maxDepth) return;
|
|
45
|
-
let entries;
|
|
46
|
-
try {
|
|
47
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
48
|
-
} catch {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
for (const e of entries) {
|
|
52
|
-
if (e.isDirectory()) {
|
|
53
|
-
if (skipDirs.has(e.name)) continue;
|
|
54
|
-
await visit(path.join(dir, e.name), depth + 1);
|
|
55
|
-
} else if (e.isFile() && match(e.name)) {
|
|
56
|
-
found.push(path.join(dir, e.name));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
await visit(root, 0);
|
|
61
|
-
return found;
|
|
62
|
-
}
|
|
63
|
-
async function detectHints(cwd) {
|
|
64
|
-
const hints = [];
|
|
65
|
-
for (const fname of ["app.json", "app.config.json"]) {
|
|
66
|
-
const txt = await readIfExists(path.join(cwd, fname));
|
|
67
|
-
if (!txt) continue;
|
|
68
|
-
try {
|
|
69
|
-
const json = JSON.parse(txt);
|
|
70
|
-
const expo = json.expo ?? json;
|
|
71
|
-
const pkg = expo?.android?.package;
|
|
72
|
-
const bid = expo?.ios?.bundleIdentifier;
|
|
73
|
-
const name = expo?.name;
|
|
74
|
-
if (pkg || bid) {
|
|
75
|
-
hints.push({
|
|
76
|
-
name,
|
|
77
|
-
packageName: typeof pkg === "string" ? pkg : void 0,
|
|
78
|
-
bundleId: typeof bid === "string" ? bid : void 0,
|
|
79
|
-
source: [fname]
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const gradleFiles = await walk(
|
|
86
|
-
cwd,
|
|
87
|
-
(n) => n === "build.gradle" || n === "build.gradle.kts",
|
|
88
|
-
4
|
|
89
|
-
);
|
|
90
|
-
for (const f of gradleFiles) {
|
|
91
|
-
const txt = await readIfExists(f);
|
|
92
|
-
if (!txt) continue;
|
|
93
|
-
const m = txt.match(/applicationId[\s=]+["']([^"']+)["']/);
|
|
94
|
-
if (m?.[1]) {
|
|
95
|
-
const pkg = m[1];
|
|
96
|
-
if (!hints.some((h) => h.packageName === pkg)) {
|
|
97
|
-
hints.push({ packageName: pkg, source: [path.relative(cwd, f)] });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const plistFiles = await walk(cwd, (n) => n === "Info.plist", 5);
|
|
102
|
-
for (const f of plistFiles) {
|
|
103
|
-
const txt = await readIfExists(f);
|
|
104
|
-
if (!txt) continue;
|
|
105
|
-
const m = txt.match(
|
|
106
|
-
/<key>CFBundleIdentifier<\/key>\s*<string>([^<]+)<\/string>/
|
|
107
|
-
);
|
|
108
|
-
if (m?.[1]) {
|
|
109
|
-
let bid = m[1];
|
|
110
|
-
if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
|
|
111
|
-
if (!hints.some((h) => h.bundleId === bid)) {
|
|
112
|
-
hints.push({ bundleId: bid, source: [path.relative(cwd, f)] });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const pbxFiles = await walk(cwd, (n) => n === "project.pbxproj", 5);
|
|
117
|
-
for (const f of pbxFiles) {
|
|
118
|
-
const txt = await readIfExists(f);
|
|
119
|
-
if (!txt) continue;
|
|
120
|
-
const matches = [...txt.matchAll(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g)];
|
|
121
|
-
for (const m of matches) {
|
|
122
|
-
const bid = m[1].trim().replace(/^["']|["']$/g, "");
|
|
123
|
-
if (!bid || bid.includes("$")) continue;
|
|
124
|
-
if (!hints.some((h) => h.bundleId === bid)) {
|
|
125
|
-
hints.push({ bundleId: bid, source: [path.relative(cwd, f)] });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const pkgJson = await readIfExists(path.join(cwd, "package.json"));
|
|
130
|
-
if (pkgJson) {
|
|
131
|
-
try {
|
|
132
|
-
const json = JSON.parse(pkgJson);
|
|
133
|
-
if (typeof json.name === "string") {
|
|
134
|
-
for (const h of hints) if (!h.name) h.name = json.name;
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const merged = [];
|
|
140
|
-
const androidOnly = hints.filter((h) => h.packageName && !h.bundleId);
|
|
141
|
-
const iosOnly = hints.filter((h) => h.bundleId && !h.packageName);
|
|
142
|
-
const both = hints.filter((h) => h.packageName && h.bundleId);
|
|
143
|
-
merged.push(...both);
|
|
144
|
-
for (const a of androidOnly) {
|
|
145
|
-
const match = iosOnly.find((i) => i.bundleId === a.packageName);
|
|
146
|
-
if (match) {
|
|
147
|
-
merged.push({
|
|
148
|
-
name: a.name ?? match.name,
|
|
149
|
-
packageName: a.packageName,
|
|
150
|
-
bundleId: match.bundleId,
|
|
151
|
-
source: [...a.source, ...match.source]
|
|
152
|
-
});
|
|
153
|
-
} else {
|
|
154
|
-
merged.push(a);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
for (const i of iosOnly) {
|
|
158
|
-
if (!androidOnly.some((a) => a.packageName === i.bundleId)) {
|
|
159
|
-
merged.push(i);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return merged.filter((h) => h.packageName || h.bundleId);
|
|
163
|
-
}
|
|
164
|
-
async function hasAnyProjectSignal(cwd) {
|
|
165
|
-
return await pathExists(path.join(cwd, "package.json")) || await pathExists(path.join(cwd, "app.json")) || await pathExists(path.join(cwd, "android")) || await pathExists(path.join(cwd, "ios"));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
30
|
// src/handshake.ts
|
|
169
31
|
import http from "http";
|
|
170
32
|
async function awaitHandshake(timeoutMs) {
|
|
@@ -214,50 +76,7 @@ async function awaitHandshake(timeoutMs) {
|
|
|
214
76
|
return { port, promise };
|
|
215
77
|
}
|
|
216
78
|
|
|
217
|
-
// src/config.ts
|
|
218
|
-
import fs2 from "fs/promises";
|
|
219
|
-
import path2 from "path";
|
|
220
|
-
import os from "os";
|
|
221
|
-
var CONFIG_DIR = path2.join(os.homedir(), ".mimi-seed");
|
|
222
|
-
var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
|
|
223
|
-
async function readConfig() {
|
|
224
|
-
try {
|
|
225
|
-
const txt = await fs2.readFile(CONFIG_PATH, "utf8");
|
|
226
|
-
return JSON.parse(txt);
|
|
227
|
-
} catch {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
async function writeConfig(cfg) {
|
|
232
|
-
await fs2.mkdir(CONFIG_DIR, { recursive: true });
|
|
233
|
-
await fs2.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
234
|
-
if (process.platform !== "win32") {
|
|
235
|
-
await fs2.chmod(CONFIG_PATH, 384);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async function deleteConfig() {
|
|
239
|
-
await fs2.rm(CONFIG_PATH, { force: true });
|
|
240
|
-
}
|
|
241
|
-
var CONFIG_LOCATION = CONFIG_PATH;
|
|
242
|
-
async function getEffectiveConfig() {
|
|
243
|
-
const envToken = process.env.MIMI_SEED_TOKEN;
|
|
244
|
-
if (envToken) {
|
|
245
|
-
const webBase = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
|
|
246
|
-
return {
|
|
247
|
-
token: envToken,
|
|
248
|
-
prefix: envToken.slice(0, 8),
|
|
249
|
-
endpoint: `${webBase}/api/mcp`,
|
|
250
|
-
webBase,
|
|
251
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return readConfig();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
79
|
// src/doctor.ts
|
|
258
|
-
import fs4 from "fs";
|
|
259
|
-
import os2 from "os";
|
|
260
|
-
import path4 from "path";
|
|
261
80
|
import kleur from "kleur";
|
|
262
81
|
|
|
263
82
|
// src/git.ts
|
|
@@ -301,22 +120,22 @@ function formatCommitsForPrompt(commits) {
|
|
|
301
120
|
}
|
|
302
121
|
|
|
303
122
|
// src/project-manifest.ts
|
|
304
|
-
import
|
|
305
|
-
import
|
|
123
|
+
import fs from "fs";
|
|
124
|
+
import path from "path";
|
|
306
125
|
var MANIFEST_FILENAME = ".mimi-seed.json";
|
|
307
126
|
function findProjectManifest(startDir = process.cwd(), maxDepth = 8) {
|
|
308
|
-
let dir =
|
|
127
|
+
let dir = path.resolve(startDir);
|
|
309
128
|
for (let i = 0; i <= maxDepth; i++) {
|
|
310
|
-
const candidate =
|
|
311
|
-
if (
|
|
129
|
+
const candidate = path.join(dir, MANIFEST_FILENAME);
|
|
130
|
+
if (fs.existsSync(candidate)) {
|
|
312
131
|
try {
|
|
313
|
-
const obj = JSON.parse(
|
|
132
|
+
const obj = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
314
133
|
if (obj && typeof obj === "object") return { manifest: obj, filePath: candidate };
|
|
315
134
|
} catch {
|
|
316
135
|
}
|
|
317
136
|
return null;
|
|
318
137
|
}
|
|
319
|
-
const parent =
|
|
138
|
+
const parent = path.dirname(dir);
|
|
320
139
|
if (parent === dir) break;
|
|
321
140
|
dir = parent;
|
|
322
141
|
}
|
|
@@ -379,63 +198,34 @@ async function cmdDoctor() {
|
|
|
379
198
|
ok("Mimi Seed \uC11C\uBC84 \uC5F0\uACB0\uB428", `\uC571 ${lines.length}\uAC1C`);
|
|
380
199
|
}
|
|
381
200
|
}
|
|
382
|
-
section("\uB85C\uCEEC
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return fs4.readdirSync(path4.join(credDir, "play-service-accounts")).some((f) => f.endsWith(".json"));
|
|
395
|
-
} catch {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
})();
|
|
399
|
-
const oauthPresent = hasFile("tokens.json");
|
|
400
|
-
const appstorePresent = hasFile("appstore.json");
|
|
401
|
-
const bigquerySaPresent = hasPrefix("bigquery");
|
|
402
|
-
const creds = [
|
|
403
|
-
["Google OAuth (Firebase/AdMob/Play/Ads)", oauthPresent, "mimi-seed auth login"],
|
|
404
|
-
["App Store Connect", appstorePresent, "mimi-seed auth appstore"],
|
|
405
|
-
["Play \uC11C\uBE44\uC2A4 \uACC4\uC815 (\uC120\uD0DD \u2014 OAuth\uB85C\uB3C4 \uAC00\uB2A5)", hasPlaySa, "mimi-seed auth playstore"],
|
|
406
|
-
["BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815", bigquerySaPresent, "mimi-seed auth bigquery"]
|
|
407
|
-
];
|
|
408
|
-
for (const [label, present, cmd] of creds) {
|
|
409
|
-
if (present) ok(label);
|
|
410
|
-
else warn(label, `\u2192 ${cmd}`);
|
|
411
|
-
}
|
|
412
|
-
process.stdout.write(kleur.dim(" OAuth \uD1A0\uD070 \uC2E0\uC120\uB3C4 \uD655\uC778: mimi-seed auth status\n"));
|
|
201
|
+
section("\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 (~/.mimi-seed)");
|
|
202
|
+
migrateLegacyJenkins();
|
|
203
|
+
const detected = detectAll();
|
|
204
|
+
for (const spec of CREDENTIALS) {
|
|
205
|
+
const d = detected.get(spec.id);
|
|
206
|
+
const label = spec.note ? `${spec.label} (${spec.note})` : spec.label;
|
|
207
|
+
if (d.present) ok(spec.label, d.detail);
|
|
208
|
+
else if (isSatisfied(spec, detected)) warn(label, "\uD3F4\uBC31\uC73C\uB85C \uB3D9\uC791 \uC911");
|
|
209
|
+
else if (spec.requirement === "optional") warn(`${label}`, `\u2192 ${spec.fix}`);
|
|
210
|
+
else fail(spec.label, `\u2192 ${spec.fix}`);
|
|
211
|
+
}
|
|
212
|
+
process.stdout.write(kleur.dim(" \uC804\uBD80 \uC5F0\uACB0\uD558\uAE30: mimi-seed setup \xB7 OAuth \uC2E0\uC120\uB3C4: mimi-seed auth status\n"));
|
|
413
213
|
const loaded = findProjectManifest(cwd);
|
|
414
214
|
if (loaded) {
|
|
415
215
|
const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? "\uC774 \uD504\uB85C\uC81D\uD2B8";
|
|
416
216
|
section(`${projName} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`);
|
|
417
|
-
const connectedOf = {
|
|
418
|
-
oauth: oauthPresent,
|
|
419
|
-
bigquery: bigquerySaPresent || oauthPresent,
|
|
420
|
-
playstore: hasPlaySa || oauthPresent,
|
|
421
|
-
appstore: appstorePresent,
|
|
422
|
-
jenkins: false
|
|
423
|
-
// mimi-seed 로컬 자격증명 대상 아님(별도 MCP) — note 로만 안내
|
|
424
|
-
};
|
|
425
|
-
const fixOf = {
|
|
426
|
-
oauth: "mimi-seed auth login",
|
|
427
|
-
bigquery: "mimi-seed auth bigquery",
|
|
428
|
-
playstore: "mimi-seed auth playstore",
|
|
429
|
-
appstore: "mimi-seed auth appstore",
|
|
430
|
-
jenkins: "claude mcp add <your-jenkins-mcp> -s user"
|
|
431
|
-
};
|
|
432
217
|
for (const [id, svc] of manifestServiceEntries(loaded.manifest)) {
|
|
433
218
|
const required = svc.required !== false;
|
|
434
|
-
const connected = connectedOf[id];
|
|
435
219
|
const detail = manifestDetail(id, svc);
|
|
220
|
+
const spec = tryCredById(id);
|
|
221
|
+
if (!spec) {
|
|
222
|
+
warn(`${id} (\uC54C \uC218 \uC5C6\uB294 \uC11C\uBE44\uC2A4)`, svc.note ?? detail);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const connected = isSatisfied(spec, detected);
|
|
436
226
|
if (connected) ok(id, detail);
|
|
437
227
|
else if (!required) warn(`${id} (\uC120\uD0DD)`, svc.note ?? detail);
|
|
438
|
-
else fail(id, `\u2192 ${
|
|
228
|
+
else fail(id, `\u2192 ${spec.fix}${detail ? " " + detail : ""}`);
|
|
439
229
|
}
|
|
440
230
|
}
|
|
441
231
|
section("\uB85C\uCEEC \uD658\uACBD");
|
|
@@ -443,10 +233,8 @@ async function cmdDoctor() {
|
|
|
443
233
|
const [, major] = nodeVer.match(/v(\d+)/) ?? [];
|
|
444
234
|
if (Number(major) >= 20) {
|
|
445
235
|
ok("Node.js", nodeVer);
|
|
446
|
-
} else if (Number(major) >= 18) {
|
|
447
|
-
warn("Node.js", `${nodeVer} \u2014 CLI \uB294 \uB3D9\uC791\uD558\uC9C0\uB9CC Local MCP \uC11C\uBC84(@yoonion/mimi-seed-mcp)\uB294 v20 \uC774\uC0C1 \uD544\uC694`);
|
|
448
236
|
} else {
|
|
449
|
-
fail("Node.js", `${nodeVer} \u2014
|
|
237
|
+
fail("Node.js", `${nodeVer} \u2014 v20 \uC774\uC0C1 \uD544\uC694 (.nvmrc \uCC38\uACE0)`);
|
|
450
238
|
}
|
|
451
239
|
if (isGitRepo(cwd)) {
|
|
452
240
|
const latestTag = getLatestTag(cwd);
|
|
@@ -455,11 +243,6 @@ async function cmdDoctor() {
|
|
|
455
243
|
} else {
|
|
456
244
|
warn("Git \uC800\uC7A5\uC18C \uC5C6\uC74C", "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00");
|
|
457
245
|
}
|
|
458
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
459
|
-
ok("ANTHROPIC_API_KEY", "AI \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 \uC0AC\uC6A9 \uAC00\uB2A5");
|
|
460
|
-
} else {
|
|
461
|
-
warn("ANTHROPIC_API_KEY \uC5C6\uC74C", "\uC124\uC815 \uC2DC AI \uB9B4\uB9AC\uC988 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131 \uAC00\uB2A5");
|
|
462
|
-
}
|
|
463
246
|
section("\uC571 \uAC10\uC9C0");
|
|
464
247
|
const hints = await detectHints(cwd);
|
|
465
248
|
if (hints.length === 0) {
|
|
@@ -910,13 +693,7 @@ async function cmdReview(argv) {
|
|
|
910
693
|
}
|
|
911
694
|
|
|
912
695
|
// src/auth.ts
|
|
913
|
-
import { spawn } from "child_process";
|
|
914
|
-
import fs5 from "fs";
|
|
915
|
-
import os3 from "os";
|
|
916
|
-
import path5 from "path";
|
|
917
696
|
import kleur5 from "kleur";
|
|
918
|
-
var MCP_PKG = "@yoonion/mimi-seed-mcp";
|
|
919
|
-
var MIMI_DIR = path5.join(os3.homedir(), ".mimi-seed");
|
|
920
697
|
function log(msg) {
|
|
921
698
|
process.stdout.write(msg + "\n");
|
|
922
699
|
}
|
|
@@ -934,8 +711,18 @@ ${kleur5.bold("\uD50C\uB7AB\uD3FC\uBCC4 \uC790\uACA9\uC99D\uBA85:")}
|
|
|
934
711
|
${kleur5.cyan("mimi-seed auth playstore")} Play \uC11C\uBE44\uC2A4 \uACC4\uC815 JSON \uB4F1\uB85D
|
|
935
712
|
${kleur5.cyan("mimi-seed auth bigquery")} BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815 (Crashlytics export \uB4F1)
|
|
936
713
|
|
|
714
|
+
${kleur5.bold("\uBE4C\uB4DC / \uB9C8\uCF00\uD305:")}
|
|
715
|
+
${kleur5.cyan("mimi-seed auth jenkins")} Jenkins \uC5F0\uACB0 (jenkins.json)
|
|
716
|
+
${kleur5.cyan("mimi-seed auth ci")} GitHub Actions / GitLab CI (ci.json)
|
|
717
|
+
${kleur5.cyan("mimi-seed auth googleads")} Google Ads (google-ads.json)
|
|
718
|
+
${kleur5.cyan("mimi-seed auth facebook")} Facebook \uD398\uC774\uC9C0
|
|
719
|
+
${kleur5.cyan("mimi-seed auth instagram")} Instagram
|
|
720
|
+
|
|
937
721
|
${kleur5.bold("\uC804\uCCB4 \uC0C1\uD0DC:")}
|
|
938
|
-
${kleur5.cyan("mimi-seed auth status --all")}
|
|
722
|
+
${kleur5.cyan("mimi-seed auth status --all")} \uBAA8\uB4E0 \uC790\uACA9\uC99D\uBA85 \uBCF4\uC720 \uC5EC\uBD80 \uD55C\uB208\uC5D0
|
|
723
|
+
|
|
724
|
+
${kleur5.bold("\uD55C \uBC88\uC5D0 \uB2E4 \uC5F0\uACB0:")}
|
|
725
|
+
${kleur5.cyan("mimi-seed setup")} \uC548\uB0B4\uB97C \uB530\uB77C\uAC00\uBA70 \uC21C\uC11C\uB300\uB85C \uC5F0\uACB0 (\uAD8C\uC7A5)
|
|
939
726
|
|
|
940
727
|
${kleur5.bold("login \uC635\uC158:")}
|
|
941
728
|
--no-browser URL \uC790\uB3D9 \uC624\uD508 \uC548 \uD568 (\uC9C1\uC811 \uBCF5\uBD99)
|
|
@@ -944,44 +731,16 @@ ${kleur5.bold("login \uC635\uC158:")}
|
|
|
944
731
|
|
|
945
732
|
${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-*-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`);
|
|
946
733
|
}
|
|
947
|
-
async function runMcpBin(bin, extraArgs) {
|
|
948
|
-
return new Promise((resolve) => {
|
|
949
|
-
const child = spawn("npx", ["-y", `${MCP_PKG}`, bin, ...extraArgs], {
|
|
950
|
-
stdio: "inherit",
|
|
951
|
-
shell: true
|
|
952
|
-
});
|
|
953
|
-
child.on("error", (e) => {
|
|
954
|
-
process.stderr.write(`
|
|
955
|
-
\u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
|
|
956
|
-
`);
|
|
957
|
-
resolve(1);
|
|
958
|
-
});
|
|
959
|
-
child.on("exit", (code) => resolve(code ?? 0));
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
function fileExists(name) {
|
|
963
|
-
return fs5.existsSync(path5.join(MIMI_DIR, name));
|
|
964
|
-
}
|
|
965
|
-
function anyFileStarting(prefix) {
|
|
966
|
-
try {
|
|
967
|
-
return fs5.readdirSync(MIMI_DIR).some((f) => f.startsWith(prefix));
|
|
968
|
-
} catch {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
734
|
function printAllCredStatus() {
|
|
973
735
|
log(kleur5.bold("\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC0C1\uD0DC ") + kleur5.dim("(~/.mimi-seed)"));
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
for (const [label, ok2, cmd] of rows) {
|
|
981
|
-
const mark = ok2 ? kleur5.green("\u2713") : kleur5.red("\u2717");
|
|
982
|
-
const hint = ok2 ? "" : kleur5.dim(` \u2192 ${cmd}`);
|
|
983
|
-
log(` ${mark} ${label}${hint}`);
|
|
736
|
+
const detected = detectAll();
|
|
737
|
+
for (const spec of CREDENTIALS) {
|
|
738
|
+
const d = detected.get(spec.id);
|
|
739
|
+
const mark = d.present ? kleur5.green("\u2713") : isSatisfied(spec, detected) ? kleur5.yellow("~") : spec.requirement === "optional" ? kleur5.dim("\xB7") : kleur5.red("\u2717");
|
|
740
|
+
const tail = d.present ? kleur5.dim(d.detail ?? "") : kleur5.dim(`\u2192 ${spec.fix}`);
|
|
741
|
+
log(` ${mark} ${spec.label.padEnd(24)} ${tail}`);
|
|
984
742
|
}
|
|
743
|
+
log(kleur5.dim("\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup"));
|
|
985
744
|
}
|
|
986
745
|
async function cmdAuth(args) {
|
|
987
746
|
const sub = args[0];
|
|
@@ -997,6 +756,15 @@ async function cmdAuth(args) {
|
|
|
997
756
|
if (sub === "appstore") return void exitWith(await runMcpBin("mimi-seed-appstore-auth", rest));
|
|
998
757
|
if (sub === "playstore") return void exitWith(await runMcpBin("mimi-seed-playstore-auth", rest));
|
|
999
758
|
if (sub === "bigquery") return void exitWith(await runMcpBin("mimi-seed-bigquery-auth", rest));
|
|
759
|
+
if (sub === "jenkins") return void exitWith(await runMcpBin("mimi-seed-jenkins-auth", rest));
|
|
760
|
+
if (sub === "googleads") return void exitWith(await runMcpBin("mimi-seed-googleads-auth", rest));
|
|
761
|
+
if (sub === "facebook") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["facebook"]));
|
|
762
|
+
if (sub === "instagram") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["instagram"]));
|
|
763
|
+
if (sub === "ci") {
|
|
764
|
+
const { cmdSetup: cmdSetup2 } = await import("./setup-ENJIMGJF.js");
|
|
765
|
+
await cmdSetup2(["--only", "github,gitlab", "--reconnect", "github,gitlab"]);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
1000
768
|
let mcpArgs;
|
|
1001
769
|
switch (sub) {
|
|
1002
770
|
case void 0:
|
|
@@ -1024,11 +792,11 @@ function exitWith(code) {
|
|
|
1024
792
|
}
|
|
1025
793
|
|
|
1026
794
|
// src/cloud.ts
|
|
1027
|
-
import { spawn
|
|
795
|
+
import { spawn } from "child_process";
|
|
1028
796
|
var MCP_PKG2 = "@yoonion/mimi-seed-mcp";
|
|
1029
797
|
async function runMcpBin2(bin, extraArgs) {
|
|
1030
798
|
return new Promise((resolve) => {
|
|
1031
|
-
const child =
|
|
799
|
+
const child = spawn("npx", ["-y", `${MCP_PKG2}`, bin, ...extraArgs], {
|
|
1032
800
|
stdio: "inherit",
|
|
1033
801
|
shell: true
|
|
1034
802
|
});
|
|
@@ -1054,541 +822,19 @@ async function cmdGa4(args) {
|
|
|
1054
822
|
exitWith2(await runMcpBin2("mimi-seed-ga4", args));
|
|
1055
823
|
}
|
|
1056
824
|
|
|
1057
|
-
// src/deploy.ts
|
|
1058
|
-
import kleur6 from "kleur";
|
|
1059
|
-
import * as readline from "readline";
|
|
1060
|
-
|
|
1061
|
-
// src/ci-providers.ts
|
|
1062
|
-
import fs6 from "fs";
|
|
1063
|
-
import path6 from "path";
|
|
1064
|
-
import os4 from "os";
|
|
1065
|
-
var CI_CONFIG_PATH = path6.join(os4.homedir(), ".mimi-seed", "ci.json");
|
|
1066
|
-
function loadCiProviderConfig() {
|
|
1067
|
-
try {
|
|
1068
|
-
return JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
|
|
1069
|
-
} catch {
|
|
1070
|
-
return null;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
function saveCiProviderConfig(cfg) {
|
|
1074
|
-
const dir = path6.dirname(CI_CONFIG_PATH);
|
|
1075
|
-
if (!fs6.existsSync(dir)) {
|
|
1076
|
-
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1077
|
-
}
|
|
1078
|
-
fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
1079
|
-
if (process.platform !== "win32") {
|
|
1080
|
-
fs6.chmodSync(CI_CONFIG_PATH, 384);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
function ghBase(cfg) {
|
|
1084
|
-
if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
|
|
1085
|
-
return "https://api.github.com";
|
|
1086
|
-
}
|
|
1087
|
-
function ghHeaders(token) {
|
|
1088
|
-
return {
|
|
1089
|
-
Authorization: `Bearer ${token}`,
|
|
1090
|
-
Accept: "application/vnd.github+json",
|
|
1091
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
1092
|
-
"Content-Type": "application/json"
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
|
|
1096
|
-
const startTime = /* @__PURE__ */ new Date();
|
|
1097
|
-
const wfId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
|
|
1098
|
-
const dispatchRes = await fetch(
|
|
1099
|
-
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/dispatches`,
|
|
1100
|
-
{
|
|
1101
|
-
method: "POST",
|
|
1102
|
-
headers: ghHeaders(cfg.token),
|
|
1103
|
-
body: JSON.stringify({ ref, inputs })
|
|
1104
|
-
}
|
|
1105
|
-
);
|
|
1106
|
-
if (!dispatchRes.ok) {
|
|
1107
|
-
throw new Error(`GitHub dispatch ${dispatchRes.status}: ${await dispatchRes.text()}`);
|
|
1108
|
-
}
|
|
1109
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
1110
|
-
const runsRes = await fetch(
|
|
1111
|
-
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/runs?per_page=5`,
|
|
1112
|
-
{ headers: ghHeaders(cfg.token) }
|
|
1113
|
-
);
|
|
1114
|
-
if (!runsRes.ok) return null;
|
|
1115
|
-
const data = await runsRes.json();
|
|
1116
|
-
const run = data.workflow_runs.find((r) => new Date(r.created_at) >= startTime) ?? data.workflow_runs[0];
|
|
1117
|
-
if (!run) return null;
|
|
1118
|
-
return { runId: run.id, url: run.html_url };
|
|
1119
|
-
}
|
|
1120
|
-
async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
|
|
1121
|
-
const start = Date.now();
|
|
1122
|
-
let consecutiveErrors = 0;
|
|
1123
|
-
while (Date.now() - start < timeoutMs) {
|
|
1124
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1125
|
-
let res;
|
|
1126
|
-
try {
|
|
1127
|
-
res = await fetch(
|
|
1128
|
-
`${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/runs/${runId}`,
|
|
1129
|
-
{ headers: ghHeaders(cfg.token) }
|
|
1130
|
-
);
|
|
1131
|
-
} catch {
|
|
1132
|
-
consecutiveErrors++;
|
|
1133
|
-
if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958 3\uD68C");
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
|
-
if (!res.ok) {
|
|
1137
|
-
consecutiveErrors++;
|
|
1138
|
-
if (consecutiveErrors >= 3) throw new Error(`GitHub API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
consecutiveErrors = 0;
|
|
1142
|
-
const data = await res.json();
|
|
1143
|
-
onTick?.(data.status);
|
|
1144
|
-
if (data.status === "completed") {
|
|
1145
|
-
if (data.conclusion === "success") return "success";
|
|
1146
|
-
if (data.conclusion === "cancelled") return "cancelled";
|
|
1147
|
-
return "failure";
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return "timeout";
|
|
1151
|
-
}
|
|
1152
|
-
function glBase(cfg) {
|
|
1153
|
-
return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
|
|
1154
|
-
}
|
|
1155
|
-
function glProjectId(cfg) {
|
|
1156
|
-
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
1157
|
-
}
|
|
1158
|
-
async function glTriggerPipeline(cfg, ref, variables = {}) {
|
|
1159
|
-
const vars = Object.entries(variables).map(([key, value]) => ({ key, value }));
|
|
1160
|
-
const body = { ref };
|
|
1161
|
-
if (vars.length > 0) body.variables = vars;
|
|
1162
|
-
const res = await fetch(`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipeline`, {
|
|
1163
|
-
method: "POST",
|
|
1164
|
-
headers: { "PRIVATE-TOKEN": cfg.token, "Content-Type": "application/json" },
|
|
1165
|
-
body: JSON.stringify(body)
|
|
1166
|
-
});
|
|
1167
|
-
if (!res.ok) throw new Error(`GitLab trigger ${res.status}: ${await res.text()}`);
|
|
1168
|
-
const data = await res.json();
|
|
1169
|
-
return { pipelineId: data.id, url: data.web_url };
|
|
1170
|
-
}
|
|
1171
|
-
async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
|
|
1172
|
-
const start = Date.now();
|
|
1173
|
-
let consecutiveErrors = 0;
|
|
1174
|
-
while (Date.now() - start < timeoutMs) {
|
|
1175
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1176
|
-
let res;
|
|
1177
|
-
try {
|
|
1178
|
-
res = await fetch(
|
|
1179
|
-
`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipelines/${pipelineId}`,
|
|
1180
|
-
{ headers: { "PRIVATE-TOKEN": cfg.token } }
|
|
1181
|
-
);
|
|
1182
|
-
} catch {
|
|
1183
|
-
consecutiveErrors++;
|
|
1184
|
-
if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958 3\uD68C");
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
if (!res.ok) {
|
|
1188
|
-
consecutiveErrors++;
|
|
1189
|
-
if (consecutiveErrors >= 3) throw new Error(`GitLab API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
|
|
1190
|
-
continue;
|
|
1191
|
-
}
|
|
1192
|
-
consecutiveErrors = 0;
|
|
1193
|
-
const data = await res.json();
|
|
1194
|
-
onTick?.(data.status);
|
|
1195
|
-
if (data.status === "success") return "success";
|
|
1196
|
-
if (data.status === "failed") return "failure";
|
|
1197
|
-
if (data.status === "canceled" || data.status === "skipped") return "cancelled";
|
|
1198
|
-
}
|
|
1199
|
-
return "timeout";
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
// src/deploy.ts
|
|
1203
|
-
var PHASE_ICON = {
|
|
1204
|
-
init: "\u{1F680}",
|
|
1205
|
-
verify: "\u{1F50D}",
|
|
1206
|
-
notes: "\u{1F4DD}",
|
|
1207
|
-
apply: "\u{1F4E4}",
|
|
1208
|
-
promote: "\u{1F3AF}",
|
|
1209
|
-
done: "\u{1F389}",
|
|
1210
|
-
error: "\u274C"
|
|
1211
|
-
};
|
|
1212
|
-
function log2(msg) {
|
|
1213
|
-
process.stdout.write(msg + "\n");
|
|
1214
|
-
}
|
|
1215
|
-
function jenkinsHeaders(cfg) {
|
|
1216
|
-
const creds = Buffer.from(`${cfg.user ?? "admin"}:${cfg.token}`).toString("base64");
|
|
1217
|
-
return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
|
|
1218
|
-
}
|
|
1219
|
-
async function triggerBuild(cfg, jobName, params) {
|
|
1220
|
-
const qs = new URLSearchParams(params).toString();
|
|
1221
|
-
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
|
|
1222
|
-
const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
|
|
1223
|
-
if (!res.ok) {
|
|
1224
|
-
throw new Error(`Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${res.status}: ${await res.text()}`);
|
|
1225
|
-
}
|
|
1226
|
-
const location = res.headers.get("Location") ?? "";
|
|
1227
|
-
const match = location.match(/\/queue\/item\/(\d+)\//);
|
|
1228
|
-
return match?.[1] ?? "";
|
|
1229
|
-
}
|
|
1230
|
-
async function getQueueBuildNumber(cfg, queueItemId) {
|
|
1231
|
-
const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
|
|
1232
|
-
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
1233
|
-
if (!res.ok) return null;
|
|
1234
|
-
const data = await res.json();
|
|
1235
|
-
return data.executable?.number ?? null;
|
|
1236
|
-
}
|
|
1237
|
-
async function getBuildStatus(cfg, jobName, buildNumber) {
|
|
1238
|
-
const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
|
|
1239
|
-
const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
|
|
1240
|
-
if (!res.ok) throw new Error(`\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${res.status}`);
|
|
1241
|
-
const data = await res.json();
|
|
1242
|
-
return {
|
|
1243
|
-
building: data.building ?? true,
|
|
1244
|
-
result: data.result ?? null,
|
|
1245
|
-
duration: data.duration ?? 0
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60 * 1e3) {
|
|
1249
|
-
const start = Date.now();
|
|
1250
|
-
const intervalMs = 15e3;
|
|
1251
|
-
let dots = 0;
|
|
1252
|
-
let consecutiveErrors = 0;
|
|
1253
|
-
while (Date.now() - start < timeoutMs) {
|
|
1254
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1255
|
-
let status;
|
|
1256
|
-
try {
|
|
1257
|
-
status = await getBuildStatus(cfg, jobName, buildNumber);
|
|
1258
|
-
consecutiveErrors = 0;
|
|
1259
|
-
} catch {
|
|
1260
|
-
consecutiveErrors++;
|
|
1261
|
-
if (consecutiveErrors >= 3) throw new Error("Jenkins \uC5F0\uACB0 \uC624\uB958 3\uD68C \uC5F0\uC18D \u2014 \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uC138\uC694");
|
|
1262
|
-
process.stdout.write(`\r \u26A0 Jenkins \uC5F0\uACB0 \uC624\uB958 (${consecutiveErrors}/3\uD68C), \uC7AC\uC2DC\uB3C4... `);
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
dots = (dots + 1) % 4;
|
|
1266
|
-
process.stdout.write(`\r \u23F3 \uBE4C\uB4DC \uC9C4\uD589 \uC911${".".repeat(dots + 1)} `);
|
|
1267
|
-
if (!status.building) {
|
|
1268
|
-
process.stdout.write("\n");
|
|
1269
|
-
return status.result ?? "FAILURE";
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
throw new Error("\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)");
|
|
1273
|
-
}
|
|
1274
|
-
async function streamDeploy(webBase, token, body) {
|
|
1275
|
-
const res = await fetch(`${webBase}/api/deploy`, {
|
|
1276
|
-
method: "POST",
|
|
1277
|
-
headers: {
|
|
1278
|
-
"Content-Type": "application/json",
|
|
1279
|
-
Authorization: `Bearer ${token}`
|
|
1280
|
-
},
|
|
1281
|
-
body: JSON.stringify(body)
|
|
1282
|
-
});
|
|
1283
|
-
if (!res.ok) {
|
|
1284
|
-
const text = await res.text().catch(() => "");
|
|
1285
|
-
throw new Error(`\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${res.status}: ${text.slice(0, 200)}`);
|
|
1286
|
-
}
|
|
1287
|
-
const reader = res.body?.getReader();
|
|
1288
|
-
if (!reader) throw new Error("SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
|
|
1289
|
-
const decoder = new TextDecoder();
|
|
1290
|
-
let buf = "";
|
|
1291
|
-
while (true) {
|
|
1292
|
-
const { done, value } = await reader.read();
|
|
1293
|
-
if (done) break;
|
|
1294
|
-
buf += decoder.decode(value, { stream: true });
|
|
1295
|
-
const lines = buf.split("\n");
|
|
1296
|
-
buf = lines.pop() ?? "";
|
|
1297
|
-
for (const line of lines) {
|
|
1298
|
-
if (!line.startsWith("data: ")) continue;
|
|
1299
|
-
try {
|
|
1300
|
-
const event = JSON.parse(line.slice(6));
|
|
1301
|
-
const icon = PHASE_ICON[event.phase] ?? "\u25B8";
|
|
1302
|
-
const color = event.status === "done" ? kleur6.green : event.status === "failed" ? kleur6.red : event.status === "skipped" ? kleur6.yellow : kleur6.dim;
|
|
1303
|
-
log2(` ${icon} ${color(event.message)}`);
|
|
1304
|
-
if (event.phase === "error" || event.phase === "verify" && event.status === "failed" || event.phase === "promote" && event.status === "failed") {
|
|
1305
|
-
process.exit(1);
|
|
1306
|
-
}
|
|
1307
|
-
} catch {
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
function parseArgs4(argv) {
|
|
1313
|
-
const args = {
|
|
1314
|
-
platform: "android",
|
|
1315
|
-
language: "ko-KR",
|
|
1316
|
-
dryRun: false,
|
|
1317
|
-
yes: false,
|
|
1318
|
-
skipBuild: false,
|
|
1319
|
-
setupJenkins: false,
|
|
1320
|
-
setupGithub: false,
|
|
1321
|
-
setupGitlab: false,
|
|
1322
|
-
ci: "auto",
|
|
1323
|
-
ref: "main"
|
|
1324
|
-
};
|
|
1325
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1326
|
-
if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
|
|
1327
|
-
if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
|
|
1328
|
-
if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
|
|
1329
|
-
if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
|
|
1330
|
-
if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
|
|
1331
|
-
if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
|
|
1332
|
-
if (argv[i] === "--dry-run") args.dryRun = true;
|
|
1333
|
-
if (argv[i] === "--yes" || argv[i] === "-y") args.yes = true;
|
|
1334
|
-
if (argv[i] === "--skip-build") args.skipBuild = true;
|
|
1335
|
-
if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
|
|
1336
|
-
if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
|
|
1337
|
-
if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
|
|
1338
|
-
if (argv[i] === "setup-jenkins") args.setupJenkins = true;
|
|
1339
|
-
if (argv[i] === "setup-github") args.setupGithub = true;
|
|
1340
|
-
if (argv[i] === "setup-gitlab") args.setupGitlab = true;
|
|
1341
|
-
}
|
|
1342
|
-
return args;
|
|
1343
|
-
}
|
|
1344
|
-
async function promptJenkinsSetup() {
|
|
1345
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1346
|
-
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
1347
|
-
log2(kleur6.bold("Jenkins \uC124\uC815"));
|
|
1348
|
-
const url = await ask(" Jenkins URL (\uC608: http://your-jenkins.example.com:8080): ");
|
|
1349
|
-
const user = await ask(" Jenkins \uC0AC\uC6A9\uC790\uBA85 (\uC608: admin): ");
|
|
1350
|
-
const token = await ask(" Jenkins API Token: ");
|
|
1351
|
-
const jobAndroid = await ask(" Android Job \uC774\uB984 (\uC608: my-app-android): ");
|
|
1352
|
-
const jobIos = await ask(" iOS Job \uC774\uB984 (\uC120\uD0DD, \uC5D4\uD130 \uC2A4\uD0B5): ");
|
|
1353
|
-
rl.close();
|
|
1354
|
-
return { url, user, token, jobAndroid: jobAndroid || void 0, jobIos: jobIos || void 0 };
|
|
1355
|
-
}
|
|
1356
|
-
async function promptGitProviderSetup(provider) {
|
|
1357
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1358
|
-
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
1359
|
-
const isGh = provider === "github";
|
|
1360
|
-
log2(kleur6.bold(isGh ? "GitHub Actions \uC124\uC815" : "GitLab CI \uC124\uC815"));
|
|
1361
|
-
const tokenLabel = isGh ? " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): " : " GitLab Personal Access Token: ";
|
|
1362
|
-
const token = await ask(tokenLabel);
|
|
1363
|
-
const owner = await ask(isGh ? " Owner (org/user): " : " Namespace/group: ");
|
|
1364
|
-
const repo = await ask(" Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ");
|
|
1365
|
-
const hostPrompt = isGh ? " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): " : " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ";
|
|
1366
|
-
const host = await ask(hostPrompt);
|
|
1367
|
-
rl.close();
|
|
1368
|
-
return {
|
|
1369
|
-
provider,
|
|
1370
|
-
token,
|
|
1371
|
-
owner,
|
|
1372
|
-
repo,
|
|
1373
|
-
host: host || void 0
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
function resolveCi(ciOption, jenkins, ciProvider) {
|
|
1377
|
-
if (ciOption !== "auto") return ciOption;
|
|
1378
|
-
if (jenkins?.url && jenkins.token) return "jenkins";
|
|
1379
|
-
if (ciProvider) return ciProvider.provider;
|
|
1380
|
-
throw new Error(
|
|
1381
|
-
"CI \uC124\uC815 \uC5C6\uC74C. \uB2E4\uC74C \uC911 \uD558\uB098 \uC2E4\uD589:\n \u2022 mimi-seed deploy setup-jenkins\n \u2022 mimi-seed deploy setup-github\n \u2022 mimi-seed deploy setup-gitlab"
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
async function runGitProviderBuild(cfg, args) {
|
|
1385
|
-
let runUrl = "";
|
|
1386
|
-
let runId;
|
|
1387
|
-
if (cfg.provider === "github") {
|
|
1388
|
-
if (!args.workflow) {
|
|
1389
|
-
throw new Error("--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)");
|
|
1390
|
-
}
|
|
1391
|
-
log2(`\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${kleur6.cyan(args.workflow)} @ ${args.ref}`);
|
|
1392
|
-
const inputs = {};
|
|
1393
|
-
if (args.appId) inputs.MIMI_APP_ID = args.appId;
|
|
1394
|
-
inputs.PLATFORM = args.platform;
|
|
1395
|
-
const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
|
|
1396
|
-
if (!result2) {
|
|
1397
|
-
throw new Error("GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
1398
|
-
}
|
|
1399
|
-
runId = result2.runId;
|
|
1400
|
-
runUrl = result2.url;
|
|
1401
|
-
log2(kleur6.dim(` Run ID: ${runId} \u2192 ${runUrl}`));
|
|
1402
|
-
} else {
|
|
1403
|
-
log2(`\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${args.ref}`);
|
|
1404
|
-
const variables = { PLATFORM: args.platform };
|
|
1405
|
-
if (args.appId) variables.MIMI_APP_ID = args.appId;
|
|
1406
|
-
const result2 = await glTriggerPipeline(cfg, args.ref, variables);
|
|
1407
|
-
runId = result2.pipelineId;
|
|
1408
|
-
runUrl = result2.url;
|
|
1409
|
-
log2(kleur6.dim(` Pipeline ID: ${runId} \u2192 ${runUrl}`));
|
|
1410
|
-
}
|
|
1411
|
-
log2(" \uC644\uB8CC \uB300\uAE30 \uC911...");
|
|
1412
|
-
let dots = 0;
|
|
1413
|
-
const onTick = (status) => {
|
|
1414
|
-
dots = (dots + 1) % 4;
|
|
1415
|
-
process.stdout.write(`\r \u23F3 ${status}${".".repeat(dots + 1)} `);
|
|
1416
|
-
};
|
|
1417
|
-
const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
|
|
1418
|
-
process.stdout.write("\n");
|
|
1419
|
-
if (result === "success") {
|
|
1420
|
-
log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${runId} \uC131\uACF5`));
|
|
1421
|
-
return runId;
|
|
1422
|
-
}
|
|
1423
|
-
log2(kleur6.red(`\uBE4C\uB4DC \uC885\uB8CC: ${result}`));
|
|
1424
|
-
log2(kleur6.dim(` ${runUrl}`));
|
|
1425
|
-
log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code <N> --platform ${args.platform}`));
|
|
1426
|
-
process.exit(1);
|
|
1427
|
-
}
|
|
1428
|
-
async function cmdDeploy(argv) {
|
|
1429
|
-
const args = parseArgs4(argv);
|
|
1430
|
-
const cfg = await getEffectiveConfig();
|
|
1431
|
-
if (!cfg) {
|
|
1432
|
-
log2(kleur6.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589."));
|
|
1433
|
-
process.exit(1);
|
|
1434
|
-
}
|
|
1435
|
-
if (args.setupJenkins) {
|
|
1436
|
-
const jenkins = await promptJenkinsSetup();
|
|
1437
|
-
await writeConfig({ ...cfg, jenkins });
|
|
1438
|
-
log2(kleur6.green("\u2705 Jenkins \uC124\uC815 \uC800\uC7A5\uB428"));
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
if (args.setupGithub) {
|
|
1442
|
-
const ciCfg = await promptGitProviderSetup("github");
|
|
1443
|
-
saveCiProviderConfig(ciCfg);
|
|
1444
|
-
log2(kleur6.green(`\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
if (args.setupGitlab) {
|
|
1448
|
-
const ciCfg = await promptGitProviderSetup("gitlab");
|
|
1449
|
-
saveCiProviderConfig(ciCfg);
|
|
1450
|
-
log2(kleur6.green(`\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1453
|
-
log2(kleur6.bold(`mimi-seed deploy \u2014 ${args.platform}`));
|
|
1454
|
-
if (args.dryRun) log2(kleur6.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
|
|
1455
|
-
log2("");
|
|
1456
|
-
let versionCode = args.versionCode;
|
|
1457
|
-
if (!args.skipBuild) {
|
|
1458
|
-
const ciProvider = loadCiProviderConfig();
|
|
1459
|
-
const kind = resolveCi(args.ci, cfg.jenkins, ciProvider);
|
|
1460
|
-
log2(kleur6.dim(` CI: ${kind}`));
|
|
1461
|
-
if (kind === "jenkins") {
|
|
1462
|
-
if (!cfg.jenkins?.url || !cfg.jenkins?.token) {
|
|
1463
|
-
log2(kleur6.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
|
|
1464
|
-
process.exit(1);
|
|
1465
|
-
}
|
|
1466
|
-
const jenkins = cfg.jenkins;
|
|
1467
|
-
const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
|
|
1468
|
-
if (!jobName) {
|
|
1469
|
-
log2(kleur6.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
|
|
1470
|
-
process.exit(1);
|
|
1471
|
-
}
|
|
1472
|
-
log2(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur6.cyan(jobName)}`);
|
|
1473
|
-
const buildParams = {};
|
|
1474
|
-
if (args.appId) buildParams.MIMI_APP_ID = args.appId;
|
|
1475
|
-
const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
|
|
1476
|
-
if (!queueItemId) {
|
|
1477
|
-
log2(kleur6.yellow(" \u26A0 Queue item ID\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uBE4C\uB4DC\uB294 \uC2DC\uC791\uB410\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
1478
|
-
} else {
|
|
1479
|
-
log2(kleur6.dim(` Queue item: ${queueItemId}`));
|
|
1480
|
-
}
|
|
1481
|
-
let buildNumber = null;
|
|
1482
|
-
if (queueItemId) {
|
|
1483
|
-
log2(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
|
|
1484
|
-
for (let i = 0; i < 6; i++) {
|
|
1485
|
-
await new Promise((r) => setTimeout(r, 5e3));
|
|
1486
|
-
buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
|
|
1487
|
-
if (buildNumber) break;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
if (!buildNumber) {
|
|
1491
|
-
log2(kleur6.yellow(" \uBE4C\uB4DC \uBC88\uD638\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --skip-build + --version-code \uB85C \uC7AC\uC2DC\uB3C4 \uAC00\uB2A5."));
|
|
1492
|
-
process.exit(1);
|
|
1493
|
-
}
|
|
1494
|
-
log2(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
|
|
1495
|
-
const result = await pollBuildComplete(jenkins, jobName, buildNumber);
|
|
1496
|
-
if (result !== "SUCCESS") {
|
|
1497
|
-
log2(kleur6.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
|
|
1498
|
-
log2(kleur6.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
|
|
1499
|
-
log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
|
|
1500
|
-
process.exit(1);
|
|
1501
|
-
}
|
|
1502
|
-
log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
|
|
1503
|
-
if (!versionCode) {
|
|
1504
|
-
versionCode = buildNumber;
|
|
1505
|
-
log2(kleur6.dim(` versionCode = buildNumber (${versionCode})`));
|
|
1506
|
-
}
|
|
1507
|
-
} else {
|
|
1508
|
-
if (!ciProvider) {
|
|
1509
|
-
log2(kleur6.red(`${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`));
|
|
1510
|
-
process.exit(1);
|
|
1511
|
-
}
|
|
1512
|
-
const buildId = await runGitProviderBuild(ciProvider, args);
|
|
1513
|
-
if (!versionCode) {
|
|
1514
|
-
log2(kleur6.red(`\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`));
|
|
1515
|
-
log2(kleur6.dim(" \uAD8C\uC7A5 \uC0AC\uD56D:"));
|
|
1516
|
-
log2(kleur6.dim(" \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825"));
|
|
1517
|
-
log2(kleur6.dim(" \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>"));
|
|
1518
|
-
process.exit(1);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
if (!versionCode) {
|
|
1523
|
-
log2(kleur6.red("versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694."));
|
|
1524
|
-
process.exit(1);
|
|
1525
|
-
}
|
|
1526
|
-
let appId = args.appId;
|
|
1527
|
-
if (!appId) {
|
|
1528
|
-
const { mcpCall: mcpCall2 } = await import("./mcp-client-26VCPY4L.js");
|
|
1529
|
-
const r = await mcpCall2(cfg.endpoint, cfg.token, "list_apps", {});
|
|
1530
|
-
if (!r.isError) {
|
|
1531
|
-
try {
|
|
1532
|
-
const apps = JSON.parse(r.text);
|
|
1533
|
-
if (apps.length > 0) {
|
|
1534
|
-
appId = apps[0].id;
|
|
1535
|
-
log2(kleur6.dim(` \uC571: ${apps[0].name} (${appId})`));
|
|
1536
|
-
}
|
|
1537
|
-
} catch {
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
if (!appId) {
|
|
1542
|
-
log2(kleur6.red("appId\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --app <id> \uB85C \uC9C0\uC815\uD558\uAC70\uB098 `mimi-seed init` \uC73C\uB85C \uC571 \uB4F1\uB85D."));
|
|
1543
|
-
process.exit(1);
|
|
1544
|
-
}
|
|
1545
|
-
const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
|
|
1546
|
-
if (needsConfirm) {
|
|
1547
|
-
const target = args.platform === "ios" ? "App Store \uC2EC\uC0AC \uC81C\uCD9C" : "Play Store production \uD2B8\uB799";
|
|
1548
|
-
log2("");
|
|
1549
|
-
log2(kleur6.yellow(`\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${args.platform} \xB7 versionCode ${versionCode} \u2192 ${target}`));
|
|
1550
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1551
|
-
const answer = await new Promise(
|
|
1552
|
-
(resolve) => rl.question(kleur6.bold("\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: "), (a) => resolve(a.trim().toLowerCase()))
|
|
1553
|
-
);
|
|
1554
|
-
rl.close();
|
|
1555
|
-
if (answer !== "y" && answer !== "yes") {
|
|
1556
|
-
log2(kleur6.dim("\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)"));
|
|
1557
|
-
return;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
log2("");
|
|
1561
|
-
log2("\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...");
|
|
1562
|
-
log2("");
|
|
1563
|
-
const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
|
|
1564
|
-
await streamDeploy(cfg.webBase, cfg.token, {
|
|
1565
|
-
appId,
|
|
1566
|
-
platform: args.platform,
|
|
1567
|
-
versionCode,
|
|
1568
|
-
...deployBuildNumber ? { buildNumber: deployBuildNumber } : {},
|
|
1569
|
-
fromRef: args.fromRef,
|
|
1570
|
-
toRef: args.toRef,
|
|
1571
|
-
language: args.language,
|
|
1572
|
-
dryRun: args.dryRun
|
|
1573
|
-
});
|
|
1574
|
-
log2("");
|
|
1575
|
-
log2(kleur6.bold("\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
1576
|
-
log2(kleur6.dim(" https://play.google.com/console/developers"));
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
825
|
// src/mcp-restart.ts
|
|
1580
826
|
import { execSync as execSync2 } from "child_process";
|
|
1581
|
-
import
|
|
1582
|
-
import
|
|
1583
|
-
import
|
|
1584
|
-
import
|
|
1585
|
-
function
|
|
827
|
+
import fs2 from "fs";
|
|
828
|
+
import os from "os";
|
|
829
|
+
import path2 from "path";
|
|
830
|
+
import kleur6 from "kleur";
|
|
831
|
+
function log2(msg) {
|
|
1586
832
|
process.stdout.write(msg + "\n");
|
|
1587
833
|
}
|
|
1588
834
|
function readClaudeJson() {
|
|
1589
|
-
const p =
|
|
835
|
+
const p = path2.join(os.homedir(), ".claude.json");
|
|
1590
836
|
try {
|
|
1591
|
-
return JSON.parse(
|
|
837
|
+
return JSON.parse(fs2.readFileSync(p, "utf8"));
|
|
1592
838
|
} catch {
|
|
1593
839
|
return {};
|
|
1594
840
|
}
|
|
@@ -1604,7 +850,7 @@ function findProcessMarker(cfg) {
|
|
|
1604
850
|
return meaningful.at(-1) ?? null;
|
|
1605
851
|
}
|
|
1606
852
|
function killByMarker(marker) {
|
|
1607
|
-
const isWin =
|
|
853
|
+
const isWin = os.platform() === "win32";
|
|
1608
854
|
if (isWin) {
|
|
1609
855
|
const escaped = marker.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1610
856
|
let pids = [];
|
|
@@ -1621,7 +867,7 @@ function killByMarker(marker) {
|
|
|
1621
867
|
for (const pid of pids) {
|
|
1622
868
|
try {
|
|
1623
869
|
execSync2(`taskkill /F /PID ${pid}`, { stdio: "pipe" });
|
|
1624
|
-
|
|
870
|
+
log2(kleur6.dim(` PID ${pid} \uC885\uB8CC`));
|
|
1625
871
|
killed++;
|
|
1626
872
|
} catch {
|
|
1627
873
|
}
|
|
@@ -1638,47 +884,47 @@ function killByMarker(marker) {
|
|
|
1638
884
|
}
|
|
1639
885
|
async function cmdRestart(args) {
|
|
1640
886
|
const serverName = args[0] ?? "mimi-seed";
|
|
1641
|
-
|
|
1642
|
-
|
|
887
|
+
log2(kleur6.bold(`MCP \uC11C\uBC84 \uC7AC\uC2DC\uC791: ${serverName}`));
|
|
888
|
+
log2("");
|
|
1643
889
|
const d = readClaudeJson();
|
|
1644
890
|
const servers = d.mcpServers;
|
|
1645
891
|
const cfg = servers?.[serverName];
|
|
1646
892
|
if (!cfg) {
|
|
1647
893
|
const available = servers ? Object.keys(servers).join(", ") : "(\uC5C6\uC74C)";
|
|
1648
|
-
|
|
1649
|
-
|
|
894
|
+
log2(kleur6.red(`'${serverName}' \uC11C\uBC84\uB97C ~/.claude/.claude.json\uC5D0\uC11C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
895
|
+
log2(kleur6.dim(` \uB4F1\uB85D\uB41C \uC11C\uBC84: ${available}`));
|
|
1650
896
|
process.exit(1);
|
|
1651
897
|
}
|
|
1652
898
|
if (cfg.type === "http" || cfg.type === "sse") {
|
|
1653
|
-
|
|
1654
|
-
|
|
899
|
+
log2(kleur6.yellow(`'${serverName}'\uB294 HTTP/SSE \uC11C\uBC84\uC785\uB2C8\uB2E4. \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
900
|
+
log2(kleur6.dim(" Claude Code\uC5D0\uC11C /mcp \uB97C \uC2E4\uD589\uD574 \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
|
|
1655
901
|
return;
|
|
1656
902
|
}
|
|
1657
903
|
const marker = findProcessMarker(cfg);
|
|
1658
904
|
if (!marker) {
|
|
1659
|
-
|
|
1660
|
-
|
|
905
|
+
log2(kleur6.yellow("\uD504\uB85C\uC138\uC2A4 \uC2DD\uBCC4\uC790(\uC2A4\uD06C\uB9BD\uD2B8 \uACBD\uB85C)\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."));
|
|
906
|
+
log2(kleur6.dim(` \uC124\uC815: ${JSON.stringify(cfg)}`));
|
|
1661
907
|
process.exit(1);
|
|
1662
908
|
}
|
|
1663
|
-
|
|
909
|
+
log2(kleur6.dim(` \uC2DD\uBCC4\uC790: ${marker}`));
|
|
1664
910
|
const { killed } = killByMarker(marker);
|
|
1665
911
|
if (killed === 0) {
|
|
1666
|
-
|
|
1667
|
-
|
|
912
|
+
log2(kleur6.yellow("\u26A0 \uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."));
|
|
913
|
+
log2(kleur6.dim(" \uC774\uBBF8 \uC885\uB8CC\uB410\uAC70\uB098, Claude Code\uAC00 \uC544\uC9C1 \uC11C\uBC84\uB97C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC740 \uC0C1\uD0DC\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
1668
914
|
} else {
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
915
|
+
log2("");
|
|
916
|
+
log2(kleur6.green(`\u2713 ${serverName} \uC885\uB8CC\uB428 (${killed}\uAC1C \uD504\uB85C\uC138\uC2A4)`));
|
|
917
|
+
log2(kleur6.dim(" Claude Code\uAC00 \uB2E4\uC74C \uB3C4\uAD6C \uD638\uCD9C \uC2DC \uC790\uB3D9\uC73C\uB85C \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4."));
|
|
1672
918
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
919
|
+
log2("");
|
|
920
|
+
log2(kleur6.cyan(" \uC5F0\uACB0 \uD655\uC778: Claude Code\uC5D0\uC11C /mcp \uC2E4\uD589"));
|
|
1675
921
|
}
|
|
1676
922
|
|
|
1677
923
|
// src/mcp-config.ts
|
|
1678
|
-
import
|
|
1679
|
-
import
|
|
1680
|
-
import
|
|
1681
|
-
import
|
|
924
|
+
import fs3 from "fs/promises";
|
|
925
|
+
import path3 from "path";
|
|
926
|
+
import os2 from "os";
|
|
927
|
+
import kleur7 from "kleur";
|
|
1682
928
|
var SERVER_NAME = "mimi-seed";
|
|
1683
929
|
function tomlString(value) {
|
|
1684
930
|
return JSON.stringify(value);
|
|
@@ -1710,35 +956,35 @@ function claudeMcpAddCommand(cfg) {
|
|
|
1710
956
|
function printMcpSetup(cfg) {
|
|
1711
957
|
process.stdout.write(
|
|
1712
958
|
[
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
959
|
+
kleur7.bold("Claude Code MCP \uB4F1\uB85D \u2014 \uC544\uB798 \uD55C \uC904\uC744 \uADF8\uB300\uB85C \uC2E4\uD589\uD558\uC138\uC694:"),
|
|
960
|
+
kleur7.cyan(` ${claudeMcpAddCommand(cfg)}`),
|
|
961
|
+
kleur7.dim(` \u26A0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3EC\uD568\uB41C \uBA85\uB839\uC785\uB2C8\uB2E4 (\uC178 \uD788\uC2A4\uD1A0\uB9AC\uC5D0 \uB0A8\uC74C). \uD1A0\uD070\uC740 ${CONFIG_LOCATION_HINT} \uC5D0\uB3C4 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.`),
|
|
1716
962
|
"",
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
963
|
+
kleur7.dim("Codex MCP \uB4F1\uB85D:"),
|
|
964
|
+
kleur7.dim(" mimi-seed mcp codex --write"),
|
|
965
|
+
kleur7.dim(" # \uB610\uB294 \uC218\uB3D9\uC73C\uB85C ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed] \uCD94\uAC00")
|
|
1720
966
|
].join("\n") + "\n"
|
|
1721
967
|
);
|
|
1722
968
|
}
|
|
1723
969
|
async function writeCodexMcpConfig(cfg) {
|
|
1724
|
-
const configDir =
|
|
1725
|
-
const configPath =
|
|
1726
|
-
await
|
|
970
|
+
const configDir = path3.join(os2.homedir(), ".codex");
|
|
971
|
+
const configPath = path3.join(configDir, "config.toml");
|
|
972
|
+
await fs3.mkdir(configDir, { recursive: true });
|
|
1727
973
|
let current = "";
|
|
1728
974
|
try {
|
|
1729
|
-
current = await
|
|
975
|
+
current = await fs3.readFile(configPath, "utf8");
|
|
1730
976
|
} catch {
|
|
1731
977
|
current = "";
|
|
1732
978
|
}
|
|
1733
979
|
const next = replaceTomlBlock(current, `mcp_servers.${SERVER_NAME}`, codexBlock(cfg));
|
|
1734
|
-
await
|
|
980
|
+
await fs3.writeFile(configPath, next);
|
|
1735
981
|
return configPath;
|
|
1736
982
|
}
|
|
1737
983
|
|
|
1738
984
|
// src/release-manifest.ts
|
|
1739
|
-
import
|
|
1740
|
-
import
|
|
1741
|
-
var RELEASE_MANIFEST_RELATIVE_PATH =
|
|
985
|
+
import fs4 from "fs/promises";
|
|
986
|
+
import path4 from "path";
|
|
987
|
+
var RELEASE_MANIFEST_RELATIVE_PATH = path4.join("docs", "releases.json");
|
|
1742
988
|
var RELEASE_MANIFEST_TEMPLATE = {
|
|
1743
989
|
$schema: {
|
|
1744
990
|
_comment: "Mimi Seed release notes SSOT. Store notes, App Store What's New, in-app announcements, and app-version rollout metadata can share this file.",
|
|
@@ -1764,14 +1010,14 @@ var RELEASE_MANIFEST_TEMPLATE = {
|
|
|
1764
1010
|
versions: {}
|
|
1765
1011
|
};
|
|
1766
1012
|
async function ensureReleaseManifest(cwd) {
|
|
1767
|
-
const filePath =
|
|
1013
|
+
const filePath = path4.join(cwd, RELEASE_MANIFEST_RELATIVE_PATH);
|
|
1768
1014
|
try {
|
|
1769
|
-
await
|
|
1015
|
+
await fs4.access(filePath);
|
|
1770
1016
|
return { path: filePath, created: false };
|
|
1771
1017
|
} catch {
|
|
1772
1018
|
}
|
|
1773
|
-
await
|
|
1774
|
-
await
|
|
1019
|
+
await fs4.mkdir(path4.dirname(filePath), { recursive: true });
|
|
1020
|
+
await fs4.writeFile(filePath, `${JSON.stringify(RELEASE_MANIFEST_TEMPLATE, null, 2)}
|
|
1775
1021
|
`, {
|
|
1776
1022
|
mode: 420
|
|
1777
1023
|
});
|
|
@@ -1781,62 +1027,62 @@ async function ensureReleaseManifest(cwd) {
|
|
|
1781
1027
|
// src/index.ts
|
|
1782
1028
|
var DEFAULT_WEB_BASE = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
|
|
1783
1029
|
var DEFAULT_MCP_ENDPOINT = `${DEFAULT_WEB_BASE}/api/mcp`;
|
|
1784
|
-
function
|
|
1030
|
+
function log3(msg) {
|
|
1785
1031
|
process.stdout.write(msg + "\n");
|
|
1786
1032
|
}
|
|
1787
1033
|
async function cmdInit(args) {
|
|
1788
1034
|
const local = args.includes("--local");
|
|
1789
1035
|
const cwd = process.cwd();
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1036
|
+
log3(kleur8.bold("Mimi Seed CLI \u2014 init"));
|
|
1037
|
+
log3(kleur8.dim(cwd));
|
|
1038
|
+
log3("");
|
|
1793
1039
|
if (!await hasAnyProjectSignal(cwd)) {
|
|
1794
|
-
|
|
1040
|
+
log3(kleur8.yellow("\u26A0 package.json / app.json / android / ios \uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD569\uB2C8\uB2E4."));
|
|
1795
1041
|
}
|
|
1796
|
-
|
|
1042
|
+
log3("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
|
|
1797
1043
|
const hints = await detectHints(cwd);
|
|
1798
1044
|
if (hints.length === 0) {
|
|
1799
|
-
|
|
1045
|
+
log3(kleur8.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
|
|
1800
1046
|
} else {
|
|
1801
1047
|
for (const h of hints) {
|
|
1802
1048
|
const tag = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
|
|
1803
|
-
|
|
1049
|
+
log3(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur8.dim(tag)}`);
|
|
1804
1050
|
}
|
|
1805
1051
|
}
|
|
1806
|
-
|
|
1052
|
+
log3("");
|
|
1807
1053
|
if (process.env.MIMI_SEED_TOKEN) {
|
|
1808
1054
|
const cfg2 = await getEffectiveConfig();
|
|
1809
1055
|
if (cfg2 && hints.length > 0) {
|
|
1810
|
-
|
|
1056
|
+
log3(kleur8.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
|
|
1811
1057
|
const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
|
|
1812
1058
|
const result = await mcpCall(cfg2.endpoint, cfg2.token, "sync_apps", { hints: payload });
|
|
1813
1059
|
if (result.isError) {
|
|
1814
|
-
|
|
1060
|
+
log3(kleur8.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
|
|
1815
1061
|
} else {
|
|
1816
|
-
for (const line of result.text.split("\n"))
|
|
1062
|
+
for (const line of result.text.split("\n")) log3(" " + line);
|
|
1817
1063
|
}
|
|
1818
1064
|
}
|
|
1819
1065
|
const manifest2 = await ensureReleaseManifest(cwd);
|
|
1820
|
-
|
|
1821
|
-
|
|
1066
|
+
log3(kleur8.dim(` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${manifest2.created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`));
|
|
1067
|
+
log3(kleur8.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
|
|
1822
1068
|
return;
|
|
1823
1069
|
}
|
|
1824
|
-
|
|
1825
|
-
const hostName =
|
|
1070
|
+
log3("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
|
|
1071
|
+
const hostName = os3.hostname().slice(0, 32);
|
|
1826
1072
|
const name = `cli-${hostName}`;
|
|
1827
1073
|
const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
|
|
1828
1074
|
const callback = `http://127.0.0.1:${port}/cb`;
|
|
1829
1075
|
const connectUrl = `${DEFAULT_WEB_BASE}/cli/connect?callback=${encodeURIComponent(callback)}&name=${encodeURIComponent(name)}`;
|
|
1830
|
-
|
|
1076
|
+
log3(kleur8.dim(` ${connectUrl}`));
|
|
1831
1077
|
await open(connectUrl);
|
|
1832
1078
|
let handshake;
|
|
1833
1079
|
try {
|
|
1834
1080
|
handshake = await promise;
|
|
1835
1081
|
} catch (e) {
|
|
1836
|
-
|
|
1082
|
+
log3(kleur8.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
|
|
1837
1083
|
process.exit(1);
|
|
1838
1084
|
}
|
|
1839
|
-
|
|
1085
|
+
log3(kleur8.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
|
|
1840
1086
|
const cfg = {
|
|
1841
1087
|
token: handshake.token,
|
|
1842
1088
|
prefix: handshake.prefix,
|
|
@@ -1845,18 +1091,18 @@ async function cmdInit(args) {
|
|
|
1845
1091
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1846
1092
|
};
|
|
1847
1093
|
await writeConfig(cfg);
|
|
1848
|
-
|
|
1849
|
-
|
|
1094
|
+
log3(kleur8.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
|
|
1095
|
+
log3("");
|
|
1850
1096
|
if (hints.length > 0) {
|
|
1851
|
-
|
|
1097
|
+
log3("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
|
|
1852
1098
|
const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
|
|
1853
1099
|
const result = await mcpCall(cfg.endpoint, cfg.token, "sync_apps", { hints: payload });
|
|
1854
1100
|
if (result.isError) {
|
|
1855
|
-
|
|
1101
|
+
log3(kleur8.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
|
|
1856
1102
|
} else {
|
|
1857
|
-
for (const line of result.text.split("\n"))
|
|
1103
|
+
for (const line of result.text.split("\n")) log3(" " + line);
|
|
1858
1104
|
}
|
|
1859
|
-
|
|
1105
|
+
log3("");
|
|
1860
1106
|
}
|
|
1861
1107
|
const appLines = hints.flatMap((h) => {
|
|
1862
1108
|
const parts = [
|
|
@@ -1900,118 +1146,137 @@ async function cmdInit(args) {
|
|
|
1900
1146
|
"- `/mimi-seed:health` \u2014 \uC5F0\uACB0 \uC0C1\uD0DC \uBE60\uB978 \uD655\uC778",
|
|
1901
1147
|
"- `/mimi-seed:review-inbox` \u2014 \uBBF8\uB2F5\uBCC0 \uB9AC\uBDF0 \uB2F5\uBCC0"
|
|
1902
1148
|
].join("\n");
|
|
1903
|
-
const claudeDir =
|
|
1904
|
-
const claudeAgentPath =
|
|
1905
|
-
if (!
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
}
|
|
1910
|
-
const codexAgentPath =
|
|
1911
|
-
if (!
|
|
1912
|
-
|
|
1913
|
-
|
|
1149
|
+
const claudeDir = path5.join(cwd, ".claude");
|
|
1150
|
+
const claudeAgentPath = path5.join(claudeDir, "mimi-seed.md");
|
|
1151
|
+
if (!fs5.existsSync(claudeAgentPath)) {
|
|
1152
|
+
fs5.mkdirSync(claudeDir, { recursive: true });
|
|
1153
|
+
fs5.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
|
|
1154
|
+
log3(kleur8.dim(` Claude \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md`));
|
|
1155
|
+
}
|
|
1156
|
+
const codexAgentPath = path5.join(cwd, "AGENTS.md");
|
|
1157
|
+
if (!fs5.existsSync(codexAgentPath)) {
|
|
1158
|
+
fs5.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
|
|
1159
|
+
log3(kleur8.dim(` Codex \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: AGENTS.md`));
|
|
1914
1160
|
}
|
|
1915
1161
|
const manifest = await ensureReleaseManifest(cwd);
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1162
|
+
log3(kleur8.dim(` \uB9B4\uB9AC\uC988 \uB178\uD2B8 SSOT: ${manifest.created ? "\uC0DD\uC131" : "\uD655\uC778"} docs/releases.json`));
|
|
1163
|
+
log3(kleur8.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
|
|
1164
|
+
log3("");
|
|
1165
|
+
log3("Claude Code \uB610\uB294 Codex\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
|
|
1166
|
+
log3(kleur8.cyan(' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'));
|
|
1167
|
+
log3(kleur8.cyan(' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"'));
|
|
1168
|
+
log3(kleur8.cyan(' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"'));
|
|
1169
|
+
log3("");
|
|
1170
|
+
log3(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur8.underline(DEFAULT_WEB_BASE + "/apps")}`);
|
|
1171
|
+
log3("");
|
|
1926
1172
|
printMcpSetup(cfg);
|
|
1927
1173
|
if (local) {
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1174
|
+
log3("");
|
|
1175
|
+
log3(kleur8.bold("\u2500\u2500 \uB85C\uCEEC MCP \uCD94\uAC00 \uC124\uC815 (--local) \u2500\u2500"));
|
|
1176
|
+
log3(kleur8.dim("\uC6D0\uACA9 MCP(PAT, \uC77D\uAE30\xB7\uC9C4\uB2E8)\uB294 \uC704\uC5D0\uC11C \uB05D. \uB85C\uCEEC MCP\uB294 Google OAuth\uB85C \uC2A4\uD1A0\uC5B4 \uC4F0\uAE30 \uB3C4\uAD6C \uC804\uCCB4\uB97C \uC9C1\uC811 \uC2E4\uD589\uD569\uB2C8\uB2E4 (Node 20+)."));
|
|
1177
|
+
log3("");
|
|
1178
|
+
log3("1) Google \uB85C\uADF8\uC778 (Firebase / AdMob / Play / Ads):");
|
|
1933
1179
|
await cmdAuth(["login"]);
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1180
|
+
log3("");
|
|
1181
|
+
log3("2) \uB85C\uCEEC MCP \uC11C\uBC84 \uB4F1\uB85D (\uC6D0\uACA9 'mimi-seed' \uC640 \uBCC4\uAC1C):");
|
|
1182
|
+
log3(kleur8.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp"));
|
|
1183
|
+
log3(kleur8.dim(' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"]'));
|
|
1184
|
+
log3("");
|
|
1185
|
+
log3("3) \uB098\uBA38\uC9C0 \uACC4\uC815 \uC5F0\uACB0 (App Store / Play / Jenkins / CI / \uC18C\uC15C \u2026):");
|
|
1186
|
+
log3(kleur8.cyan(" mimi-seed setup"));
|
|
1187
|
+
log3(kleur8.dim(" \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 \uD1A0\uD070 \uBC1C\uAE09 \uBC29\uBC95\uC744 \uC54C\uB824\uC90D\uB2C8\uB2E4."));
|
|
1938
1188
|
}
|
|
1939
1189
|
}
|
|
1940
1190
|
async function cmdStatus() {
|
|
1941
1191
|
const cfg = await getEffectiveConfig();
|
|
1942
1192
|
if (!cfg) {
|
|
1943
|
-
|
|
1193
|
+
log3(kleur8.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
|
|
1944
1194
|
process.exit(1);
|
|
1945
1195
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1196
|
+
log3(kleur8.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
|
|
1197
|
+
log3(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
|
|
1198
|
+
log3(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
|
|
1199
|
+
log3("");
|
|
1200
|
+
log3("\u{1F4CB} \uC571 \uBAA9\uB85D:");
|
|
1951
1201
|
const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
|
|
1952
1202
|
if (r.isError) {
|
|
1953
|
-
|
|
1203
|
+
log3(kleur8.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
|
|
1954
1204
|
process.exit(1);
|
|
1955
1205
|
}
|
|
1956
|
-
for (const line of r.text.split("\n"))
|
|
1206
|
+
for (const line of r.text.split("\n")) log3(" " + line);
|
|
1957
1207
|
}
|
|
1958
1208
|
async function cmdLogout() {
|
|
1959
1209
|
await deleteConfig();
|
|
1960
|
-
|
|
1961
|
-
|
|
1210
|
+
log3(kleur8.green("\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC."));
|
|
1211
|
+
log3(kleur8.dim("\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens"));
|
|
1962
1212
|
}
|
|
1963
1213
|
async function cmdMcp(args) {
|
|
1964
1214
|
const target = args[0] ?? "help";
|
|
1965
1215
|
const cfg = await getEffectiveConfig();
|
|
1966
1216
|
if (!cfg) {
|
|
1967
|
-
|
|
1217
|
+
log3(kleur8.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
|
|
1968
1218
|
process.exit(1);
|
|
1969
1219
|
}
|
|
1970
1220
|
if (target === "codex") {
|
|
1971
1221
|
if (args.includes("--write")) {
|
|
1972
1222
|
const configPath = await writeCodexMcpConfig(cfg);
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1223
|
+
log3(kleur8.green("\u2713 Codex MCP \uC124\uC815 \uC644\uB8CC"));
|
|
1224
|
+
log3(kleur8.dim(` ${configPath}`));
|
|
1225
|
+
log3(kleur8.dim(" \u26A0 config.toml\uC5D0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3C9\uBB38\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4 (\uD30C\uC77C \uAD8C\uD55C \uD655\uC778 \uAD8C\uC7A5)."));
|
|
1226
|
+
log3("");
|
|
1227
|
+
log3("Codex\uB97C \uC0C8\uB85C \uC5F4\uACE0 `/mcp` \uB610\uB294 `codex mcp list`\uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
1978
1228
|
return;
|
|
1979
1229
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1230
|
+
log3(kleur8.bold("Codex MCP \uB4F1\uB85D"));
|
|
1231
|
+
log3("");
|
|
1232
|
+
log3("\uC790\uB3D9 \uB4F1\uB85D:");
|
|
1233
|
+
log3(kleur8.cyan(" mimi-seed mcp codex --write"));
|
|
1234
|
+
log3("");
|
|
1235
|
+
log3("\uC218\uB3D9 \uB4F1\uB85D \uC608\uC2DC (~/.codex/config.toml) \u2014 \uC2E4\uC81C \uD1A0\uD070 \uD3EC\uD568:");
|
|
1236
|
+
log3(`[mcp_servers.mimi-seed]
|
|
1987
1237
|
url = "${cfg.endpoint}"
|
|
1988
1238
|
enabled = true
|
|
1989
1239
|
http_headers = { Authorization = "Bearer ${cfg.token}" }`);
|
|
1990
1240
|
return;
|
|
1991
1241
|
}
|
|
1992
1242
|
if (target === "claude") {
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1243
|
+
log3(kleur8.bold("Claude Code MCP \uB4F1\uB85D \u2014 \uC544\uB798 \uD55C \uC904\uC744 \uADF8\uB300\uB85C \uC2E4\uD589\uD558\uC138\uC694:"));
|
|
1244
|
+
log3(kleur8.cyan(` ${claudeMcpAddCommand(cfg)}`));
|
|
1245
|
+
log3(kleur8.dim(" \u26A0 \uC2E4\uC81C \uD1A0\uD070\uC774 \uD3EC\uD568\uB41C \uBA85\uB839\uC785\uB2C8\uB2E4 (\uC178 \uD788\uC2A4\uD1A0\uB9AC\uC5D0 \uB0A8\uC74C). \uD1A0\uD070\uC740 ~/.mimi-seed/config.json \uC5D0\uB3C4 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
1996
1246
|
return;
|
|
1997
1247
|
}
|
|
1998
1248
|
printMcpSetup(cfg);
|
|
1999
1249
|
}
|
|
2000
1250
|
var CMD_USAGE = {
|
|
2001
|
-
init: `${
|
|
1251
|
+
init: `${kleur8.bold("mimi-seed init")} \u2014 \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
|
|
2002
1252
|
|
|
2003
1253
|
\uC571 \uC790\uB3D9 \uAC10\uC9C0(Expo/Gradle/Info.plist/pbxproj) \u2192 \uBE0C\uB77C\uC6B0\uC800 PAT \uBC1C\uAE09(\uC6D0\uACA9 MCP) \u2192 \uC571 \uB4F1\uB85D
|
|
2004
1254
|
+ .claude/mimi-seed.md, AGENTS.md, docs/releases.json \uC0DD\uC131/\uD655\uC778.
|
|
2005
1255
|
|
|
2006
1256
|
\uC635\uC158:
|
|
2007
1257
|
--local \uCD94\uAC00\uB85C Google OAuth \uB85C\uADF8\uC778 + \uB85C\uCEEC MCP(\uC2A4\uD1A0\uC5B4 \uC4F0\uAE30 \uB3C4\uAD6C \uC804\uCCB4) \uB4F1\uB85D \uC548\uB0B4`,
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1258
|
+
setup: `${kleur8.bold("mimi-seed setup")} \u2014 \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
|
|
1259
|
+
|
|
1260
|
+
\uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uBA3C\uC800 \uBCF4\uC5EC\uC8FC\uACE0, \uC544\uC9C1 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC740 \uAC83\uB9CC \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCF8\uB2E4.
|
|
1261
|
+
\uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 "\uADF8 \uD1A0\uD070\uC744 \uC5B4\uB514\uC11C \uC5B4\uB5BB\uAC8C \uBC1C\uAE09\uBC1B\uB294\uC9C0"\uB97C \uC54C\uB824\uC900\uB2E4.
|
|
1262
|
+
\uC774\uBBF8 \uC5F0\uACB0\uB41C \uD56D\uBAA9\uC740 \uAC74\uB108\uB6F0\uBBC0\uB85C, \uC911\uAC04\uC5D0 \uADF8\uB9CC\uB450\uACE0 \uB098\uC911\uC5D0 \uB2E4\uC2DC \uC2E4\uD589\uD574\uB3C4 \uB41C\uB2E4.
|
|
1263
|
+
|
|
1264
|
+
\uC635\uC158:
|
|
1265
|
+
--only <ids> \uC9C0\uC815\uD55C \uC790\uACA9\uC99D\uBA85\uB9CC (\uC27C\uD45C \uAD6C\uBD84: oauth,appstore,jenkins \u2026)
|
|
1266
|
+
--reconnect <ids> \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uB3C4 \uB2E4\uC2DC \uC124\uC815
|
|
1267
|
+
--platform android,ios \uD50C\uB7AB\uD3FC \uAC15\uC81C \uC9C0\uC815 (\uAE30\uBCF8: \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uB3D9 \uAC10\uC9C0)
|
|
1268
|
+
--yes, -y \uC544\uBB34\uAC83\uB3C4 \uBB3B\uC9C0 \uC54A\uACE0 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825
|
|
1269
|
+
--non-interactive \uC704\uC640 \uB3D9\uC77C (CI \uC6A9)
|
|
1270
|
+
--fail-on-missing \uD544\uC218 \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC73C\uBA74 exit 1 (CI \uAC8C\uC774\uD2B8)
|
|
1271
|
+
|
|
1272
|
+
${kleur8.dim("\uBE44TTY / CI \uD658\uACBD\uC5D0\uC11C\uB294 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825\uD55C\uB2E4.")}`,
|
|
1273
|
+
status: `${kleur8.bold("mimi-seed status")} \u2014 \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D. \uC635\uC158 \uC5C6\uC74C.`,
|
|
1274
|
+
doctor: `${kleur8.bold("mimi-seed doctor")} \u2014 \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Node\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI). \uC635\uC158 \uC5C6\uC74C.`,
|
|
1275
|
+
logout: `${kleur8.bold("mimi-seed logout")} \u2014 \uB85C\uCEEC \uC124\uC815(config.json) \uC0AD\uC81C. \uC635\uC158 \uC5C6\uC74C.`,
|
|
1276
|
+
restart: `${kleur8.bold("mimi-seed restart")} \u2014 MCP \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791 (\uAE30\uBCF8: mimi-seed)
|
|
2012
1277
|
|
|
2013
1278
|
mimi-seed restart [server-name]`,
|
|
2014
|
-
notes: `${
|
|
1279
|
+
notes: `${kleur8.bold("mimi-seed notes")} \u2014 \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
|
|
2015
1280
|
|
|
2016
1281
|
\uC635\uC158:
|
|
2017
1282
|
--from <ref> \uC2DC\uC791 \uCEE4\uBC0B/\uD0DC\uADF8 (\uAE30\uBCF8: \uCD5C\uC2E0 \uD0DC\uADF8)
|
|
@@ -2020,12 +1285,12 @@ var CMD_USAGE = {
|
|
|
2020
1285
|
--apply \uC0DD\uC131 \uD6C4 \uC2A4\uD1A0\uC5B4\uC5D0 \uBC14\uB85C \uC801\uC6A9
|
|
2021
1286
|
--no-interactive CI \uBAA8\uB4DC (\uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C)
|
|
2022
1287
|
--limit <n> \uCD5C\uB300 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 30)`,
|
|
2023
|
-
check: `${
|
|
1288
|
+
check: `${kleur8.bold("mimi-seed check")} \u2014 \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
|
|
2024
1289
|
|
|
2025
1290
|
\uC635\uC158:
|
|
2026
1291
|
--app <id> \uC571 ID \uC9C0\uC815
|
|
2027
1292
|
--fail-on-blocker \uBE14\uB85C\uCEE4 \uC788\uC73C\uBA74 exit 1 (CI\uC6A9)`,
|
|
2028
|
-
review: `${
|
|
1293
|
+
review: `${kleur8.bold("mimi-seed review")} \u2014 \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
|
|
2029
1294
|
|
|
2030
1295
|
\uC635\uC158:
|
|
2031
1296
|
--text <\uB0B4\uC6A9> \uB9AC\uBDF0 \uC6D0\uBB38 (\uBBF8\uC785\uB825 \uC2DC \uB300\uD654\uD615 \uD504\uB86C\uD504\uD2B8)
|
|
@@ -2037,7 +1302,7 @@ var CMD_USAGE = {
|
|
|
2037
1302
|
--review-id <id> \uB9AC\uBDF0 ID (--apply \uC2DC \uD544\uC694)
|
|
2038
1303
|
--package-name <p> \uD328\uD0A4\uC9C0\uBA85 (--apply \uC2DC \uD544\uC694)
|
|
2039
1304
|
--no-interactive CI \uBAA8\uB4DC`,
|
|
2040
|
-
deploy: `${
|
|
1305
|
+
deploy: `${kleur8.bold("mimi-seed deploy")} \u2014 \uC571 \uC790\uB3D9 \uBC30\uD3EC (CI \uBE4C\uB4DC \u2192 Play Store/App Store)
|
|
2041
1306
|
|
|
2042
1307
|
\uC635\uC158:
|
|
2043
1308
|
--platform android|ios \uBC30\uD3EC \uD50C\uB7AB\uD3FC (\uAE30\uBCF8: android)
|
|
@@ -2053,7 +1318,7 @@ var CMD_USAGE = {
|
|
|
2053
1318
|
--workflow <file> GitHub workflow \uD30C\uC77C (\uC608: deploy.yml)
|
|
2054
1319
|
--ref <branch|tag> GitHub/GitLab \uBE0C\uB79C\uCE58/\uD0DC\uADF8 (\uAE30\uBCF8: main)
|
|
2055
1320
|
setup-jenkins / setup-github / setup-gitlab CI \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D`,
|
|
2056
|
-
mcp: `${
|
|
1321
|
+
mcp: `${kleur8.bold("mimi-seed mcp")} \u2014 Claude/Codex MCP \uC5F0\uACB0
|
|
2057
1322
|
|
|
2058
1323
|
\uC11C\uBE0C\uBA85\uB839:
|
|
2059
1324
|
mimi-seed mcp \uD604\uC7AC \uC124\uC815 + \uB4F1\uB85D \uC548\uB0B4
|
|
@@ -2064,34 +1329,37 @@ var CMD_USAGE = {
|
|
|
2064
1329
|
function printCommandHelp(cmd) {
|
|
2065
1330
|
const usage = CMD_USAGE[cmd];
|
|
2066
1331
|
if (!usage) return false;
|
|
2067
|
-
|
|
1332
|
+
log3(usage);
|
|
2068
1333
|
return true;
|
|
2069
1334
|
}
|
|
2070
1335
|
function printHelp() {
|
|
2071
|
-
|
|
1336
|
+
log3(`${kleur8.bold("mimi-seed")} \u2014 Claude Code/Codex\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
|
|
2072
1337
|
|
|
2073
|
-
${
|
|
2074
|
-
${
|
|
2075
|
-
${
|
|
2076
|
-
${
|
|
2077
|
-
${
|
|
2078
|
-
${
|
|
2079
|
-
${
|
|
2080
|
-
${
|
|
2081
|
-
${
|
|
2082
|
-
${
|
|
2083
|
-
${
|
|
2084
|
-
${
|
|
2085
|
-
${
|
|
2086
|
-
${
|
|
2087
|
-
${
|
|
1338
|
+
${kleur8.bold("\uBA85\uB839\uC5B4:")}
|
|
1339
|
+
${kleur8.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
|
|
1340
|
+
${kleur8.cyan("mimi-seed setup")} \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
|
|
1341
|
+
${kleur8.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
|
|
1342
|
+
${kleur8.cyan("mimi-seed auth")} \uC790\uACA9\uC99D\uBA85 \uAC1C\uBCC4 \uC778\uC99D (Google / App Store / Play / Jenkins / CI \u2026)
|
|
1343
|
+
${kleur8.cyan("mimi-seed firebase")} Firebase \uC571 \uC0DD\uC131\xB7config \uB2E4\uC6B4\uB85C\uB4DC\xB7GA4 \uB9C1\uD06C
|
|
1344
|
+
${kleur8.cyan("mimi-seed admob")} AdMob \uACC4\uC815\xB7\uC571\xB7\uAD11\uACE0\uB2E8\uC704 \uC870\uD68C \uBC0F \uC0DD\uC131
|
|
1345
|
+
${kleur8.cyan("mimi-seed ga4")} GA4 property\xB7data stream \uC0DD\uC131\xB7\uC870\uD68C
|
|
1346
|
+
${kleur8.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
|
|
1347
|
+
${kleur8.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
|
|
1348
|
+
${kleur8.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
|
|
1349
|
+
${kleur8.cyan("mimi-seed review")} \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
|
|
1350
|
+
${kleur8.cyan("mimi-seed deploy")} \uC571 \uC790\uB3D9 \uBC30\uD3EC (CI \u2192 Play Store/App Store)
|
|
1351
|
+
${kleur8.cyan("mimi-seed mcp")} Claude/Codex MCP \uC5F0\uACB0 \uC548\uB0B4 \uBC0F Codex \uC124\uC815 \uC4F0\uAE30
|
|
1352
|
+
${kleur8.cyan("mimi-seed restart")} MCP \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC7AC\uC2DC\uC791 (\uAE30\uBCF8: mimi-seed)
|
|
1353
|
+
${kleur8.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
|
|
2088
1354
|
|
|
2089
|
-
${
|
|
1355
|
+
${kleur8.dim("\uAC01 \uBA85\uB839 \uC0C1\uC138 \uC635\uC158:")} ${kleur8.cyan("mimi-seed <command> --help")}
|
|
2090
1356
|
|
|
2091
|
-
${
|
|
1357
|
+
${kleur8.bold("\uD658\uACBD\uBCC0\uC218:")}
|
|
2092
1358
|
MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
|
|
2093
1359
|
MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
|
|
2094
1360
|
ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
|
|
1361
|
+
MIMI_SEED_GOOGLE_CLIENT_ID / _SECRET
|
|
1362
|
+
\uC9C1\uC811 \uB9CC\uB4E0 Google OAuth \uD074\uB77C\uC774\uC5B8\uD2B8 \uC0AC\uC6A9 (\uBBF8\uC9C0\uC815 \uC2DC \uB85C\uADF8\uC778 \uB54C \uC6F9 \uCF58\uC194\uC5D0\uC11C \uBC1B\uC544\uC634)
|
|
2095
1363
|
`);
|
|
2096
1364
|
}
|
|
2097
1365
|
async function main() {
|
|
@@ -2105,6 +1373,9 @@ async function main() {
|
|
|
2105
1373
|
case "init":
|
|
2106
1374
|
await cmdInit(restArgs);
|
|
2107
1375
|
break;
|
|
1376
|
+
case "setup":
|
|
1377
|
+
await cmdSetup(restArgs);
|
|
1378
|
+
break;
|
|
2108
1379
|
case "status":
|
|
2109
1380
|
await cmdStatus();
|
|
2110
1381
|
break;
|
|
@@ -2150,13 +1421,13 @@ async function main() {
|
|
|
2150
1421
|
printHelp();
|
|
2151
1422
|
break;
|
|
2152
1423
|
default:
|
|
2153
|
-
|
|
1424
|
+
log3(kleur8.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
|
|
2154
1425
|
printHelp();
|
|
2155
1426
|
process.exit(1);
|
|
2156
1427
|
}
|
|
2157
1428
|
} catch (e) {
|
|
2158
|
-
|
|
2159
|
-
if (process.env.DEBUG)
|
|
1429
|
+
log3(kleur8.red(`\uC624\uB958: ${e.message}`));
|
|
1430
|
+
if (process.env.DEBUG) log3(e.stack ?? "");
|
|
2160
1431
|
process.exit(1);
|
|
2161
1432
|
}
|
|
2162
1433
|
}
|