mimi-seed 0.5.0 → 0.6.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.
|
@@ -3,28 +3,74 @@
|
|
|
3
3
|
// src/setup.ts
|
|
4
4
|
import kleur2 from "kleur";
|
|
5
5
|
import * as readline2 from "readline";
|
|
6
|
-
import
|
|
6
|
+
import os6 from "os";
|
|
7
7
|
|
|
8
8
|
// src/credentials.ts
|
|
9
|
+
import fs2 from "fs";
|
|
10
|
+
import os2 from "os";
|
|
11
|
+
import path2 from "path";
|
|
12
|
+
|
|
13
|
+
// src/settings.ts
|
|
9
14
|
import fs from "fs";
|
|
10
15
|
import os from "os";
|
|
11
16
|
import path from "path";
|
|
17
|
+
var DEFAULT_LANG = "ko";
|
|
18
|
+
function settingsPath(home) {
|
|
19
|
+
return path.join(home, ".mimi-seed", "settings.json");
|
|
20
|
+
}
|
|
21
|
+
function readSettings(home = os.homedir()) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(fs.readFileSync(settingsPath(home), "utf-8"));
|
|
24
|
+
} catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function writeSettings(next, home = os.homedir()) {
|
|
29
|
+
const dir = path.join(home, ".mimi-seed");
|
|
30
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
31
|
+
const merged = { ...readSettings(home), ...next };
|
|
32
|
+
fs.writeFileSync(settingsPath(home), JSON.stringify(merged, null, 2));
|
|
33
|
+
}
|
|
34
|
+
function isLangUnset(home = os.homedir()) {
|
|
35
|
+
return !process.env.MIMI_SEED_LANG && !readSettings(home).lang;
|
|
36
|
+
}
|
|
37
|
+
function isLang(v) {
|
|
38
|
+
return v === "ko" || v === "en";
|
|
39
|
+
}
|
|
40
|
+
function resolveLang(home = os.homedir()) {
|
|
41
|
+
const env = process.env.MIMI_SEED_LANG?.toLowerCase();
|
|
42
|
+
if (isLang(env)) return env;
|
|
43
|
+
const saved = readSettings(home).lang;
|
|
44
|
+
if (isLang(saved)) return saved;
|
|
45
|
+
return DEFAULT_LANG;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/credentials.ts
|
|
49
|
+
function credLabel(spec, lang = resolveLang()) {
|
|
50
|
+
return spec.label[lang];
|
|
51
|
+
}
|
|
52
|
+
function credNote(spec, lang = resolveLang()) {
|
|
53
|
+
return spec.note?.[lang];
|
|
54
|
+
}
|
|
55
|
+
function credObtain(spec, lang = resolveLang()) {
|
|
56
|
+
return spec.obtain[lang];
|
|
57
|
+
}
|
|
12
58
|
function credDir(home) {
|
|
13
|
-
return
|
|
59
|
+
return path2.join(home, ".mimi-seed");
|
|
14
60
|
}
|
|
15
61
|
function hasFile(home, name) {
|
|
16
|
-
return
|
|
62
|
+
return fs2.existsSync(path2.join(credDir(home), name));
|
|
17
63
|
}
|
|
18
64
|
function anyFileStarting(home, prefix) {
|
|
19
65
|
try {
|
|
20
|
-
return
|
|
66
|
+
return fs2.readdirSync(credDir(home)).some((f) => f.startsWith(prefix));
|
|
21
67
|
} catch {
|
|
22
68
|
return false;
|
|
23
69
|
}
|
|
24
70
|
}
|
|
25
71
|
function readJson(home, name) {
|
|
26
72
|
try {
|
|
27
|
-
return JSON.parse(
|
|
73
|
+
return JSON.parse(fs2.readFileSync(path2.join(credDir(home), name), "utf-8"));
|
|
28
74
|
} catch {
|
|
29
75
|
return null;
|
|
30
76
|
}
|
|
@@ -32,7 +78,7 @@ function readJson(home, name) {
|
|
|
32
78
|
function hasPlaySa(home) {
|
|
33
79
|
if (hasFile(home, "play-service-account.json")) return true;
|
|
34
80
|
try {
|
|
35
|
-
return
|
|
81
|
+
return fs2.readdirSync(path2.join(credDir(home), "play-service-accounts")).some((f) => f.endsWith(".json"));
|
|
36
82
|
} catch {
|
|
37
83
|
return false;
|
|
38
84
|
}
|
|
@@ -45,16 +91,25 @@ function ciDetect(home, want) {
|
|
|
45
91
|
var CREDENTIALS = [
|
|
46
92
|
{
|
|
47
93
|
id: "mimiseed",
|
|
48
|
-
label:
|
|
94
|
+
label: {
|
|
95
|
+
ko: "Mimi Seed \uACC4\uC815 (\uD074\uB77C\uC6B0\uB4DC)",
|
|
96
|
+
en: "Mimi Seed account (cloud)"
|
|
97
|
+
},
|
|
49
98
|
requirement: "required",
|
|
50
99
|
group: "core",
|
|
51
100
|
file: "~/.mimi-seed/config.json",
|
|
52
101
|
fix: "mimi-seed init",
|
|
53
102
|
setup: { kind: "command", run: "mimi-seed init" },
|
|
54
|
-
obtain:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
103
|
+
obtain: {
|
|
104
|
+
ko: [
|
|
105
|
+
"https://mimi-seed.pryzm.gg \uC5D0\uC11C \uAC00\uC785/\uB85C\uADF8\uC778\uD558\uBA74 \uB05D \u2014 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uC54C\uC544\uC11C \uD1A0\uD070\uC744 \uB118\uACA8\uC900\uB2E4.",
|
|
106
|
+
"CI \uC5D0\uC11C\uB294 MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218\uB85C \uB300\uCCB4\uD560 \uC218 \uC788\uB2E4."
|
|
107
|
+
],
|
|
108
|
+
en: [
|
|
109
|
+
"Sign up / sign in at https://mimi-seed.pryzm.gg \u2014 the browser hands the token back for you.",
|
|
110
|
+
"In CI, set the MIMI_SEED_TOKEN environment variable instead."
|
|
111
|
+
]
|
|
112
|
+
},
|
|
58
113
|
docsAnchor: "cloud-pat",
|
|
59
114
|
detect: (home) => {
|
|
60
115
|
if (process.env.MIMI_SEED_TOKEN) return { present: true, detail: "MIMI_SEED_TOKEN (env)" };
|
|
@@ -64,37 +119,62 @@ var CREDENTIALS = [
|
|
|
64
119
|
},
|
|
65
120
|
{
|
|
66
121
|
id: "oauth",
|
|
67
|
-
label:
|
|
122
|
+
label: {
|
|
123
|
+
ko: "Google OAuth",
|
|
124
|
+
en: "Google OAuth"
|
|
125
|
+
},
|
|
68
126
|
requirement: "required",
|
|
69
127
|
group: "core",
|
|
70
128
|
file: "~/.mimi-seed/tokens.json",
|
|
71
129
|
fix: "mimi-seed auth login",
|
|
72
130
|
setup: { kind: "mcp-bin", bin: "mimi-seed-auth" },
|
|
73
|
-
obtain:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
131
|
+
obtain: {
|
|
132
|
+
ko: [
|
|
133
|
+
"\uBBF8\uB9AC \uBC1C\uAE09\uBC1B\uC744 \uAC8C **\uC5C6\uB2E4**. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778 \uD55C \uBC88\uC774\uBA74 \uB05D.",
|
|
134
|
+
"\uC774 \uD1A0\uD070 \uD558\uB098\uAC00 Firebase \xB7 AdMob \xB7 Play \xB7 Google Ads \xB7 Search Console \xB7 GA4 \xB7 IAM \xB7 BigQuery \uB97C \uBAA8\uB450 \uC5F0\uB2E4.",
|
|
135
|
+
"\u26A0\uFE0F \uB85C\uADF8\uC778 \uD654\uBA74\uC5D0\uC11C \uAC70\uBD80(access_denied) \uB2F9\uD558\uBA74 \uC571\uC774 \uC544\uC9C1 '\uD14C\uC2A4\uD305' \uBAA8\uB4DC\uB77C \uADF8\uB807\uB2E4 \u2014",
|
|
136
|
+
" \uC6B4\uC601\uC790\uC5D0\uAC8C \uB124 Google \uACC4\uC815\uC744 Test users \uC5D0 \uCD94\uAC00\uD574\uB2EC\uB77C\uACE0 \uC694\uCCAD\uD574\uC57C \uD55C\uB2E4."
|
|
137
|
+
],
|
|
138
|
+
en: [
|
|
139
|
+
"**Nothing** to fetch beforehand. One browser sign-in is all it takes.",
|
|
140
|
+
"This single token unlocks Firebase \xB7 AdMob \xB7 Play \xB7 Google Ads \xB7 Search Console \xB7 GA4 \xB7 IAM \xB7 BigQuery.",
|
|
141
|
+
"\u26A0\uFE0F If the login screen blocks you (access_denied), the app is still in 'testing' mode \u2014",
|
|
142
|
+
" ask whoever operates it to add your Google account under Test users. Retrying alone never works."
|
|
143
|
+
]
|
|
144
|
+
},
|
|
79
145
|
docsAnchor: "google-oauth",
|
|
80
|
-
note:
|
|
146
|
+
note: {
|
|
147
|
+
ko: "8\uAC1C Google \uC11C\uBE44\uC2A4\uC758 \uACF5\uD1B5 \uAD00\uBB38",
|
|
148
|
+
en: "the shared gateway to 8 Google services"
|
|
149
|
+
},
|
|
81
150
|
detect: (home) => ({ present: hasFile(home, "tokens.json") })
|
|
82
151
|
},
|
|
83
152
|
{
|
|
84
153
|
id: "appstore",
|
|
85
|
-
label:
|
|
154
|
+
label: {
|
|
155
|
+
ko: "App Store Connect",
|
|
156
|
+
en: "App Store Connect"
|
|
157
|
+
},
|
|
86
158
|
requirement: "platform",
|
|
87
159
|
platform: "ios",
|
|
88
160
|
group: "core",
|
|
89
161
|
file: "~/.mimi-seed/appstore.json",
|
|
90
162
|
fix: "mimi-seed auth appstore",
|
|
91
163
|
setup: { kind: "mcp-bin", bin: "mimi-seed-appstore-auth" },
|
|
92
|
-
obtain:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
164
|
+
obtain: {
|
|
165
|
+
ko: [
|
|
166
|
+
"\uC720\uB8CC Apple Developer Program \uBA64\uBC84\uC2ED + Admin/App Manager \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
|
|
167
|
+
"App Store Connect \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uC561\uC138\uC2A4 \u2192 \uD1B5\uD569 \u2192 App Store Connect API \u2192 \uD0A4 \uC0DD\uC131",
|
|
168
|
+
"\uAC00\uC838\uC62C \uAC83 3\uAC1C: Issuer ID \xB7 Key ID \xB7 .p8 \uD30C\uC77C",
|
|
169
|
+
"\u26A0\uFE0F .p8 \uC740 **\uB531 \uD55C \uBC88\uB9CC** \uB2E4\uC6B4\uB85C\uB4DC\uB41C\uB2E4. \uC783\uC5B4\uBC84\uB9AC\uBA74 \uD0A4\uB97C \uD3D0\uAE30\uD558\uACE0 \uC0C8\uB85C \uB9CC\uB4E4\uC5B4\uC57C \uD55C\uB2E4."
|
|
170
|
+
],
|
|
171
|
+
en: [
|
|
172
|
+
"You need a **paid** Apple Developer Program membership and the Admin / App Manager role first.",
|
|
173
|
+
"App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 App Store Connect API \u2192 generate a key",
|
|
174
|
+
"Collect three things: Issuer ID \xB7 Key ID \xB7 the .p8 file",
|
|
175
|
+
"\u26A0\uFE0F The .p8 downloads **exactly once**. Lose it and you must revoke the key and issue a new one."
|
|
176
|
+
]
|
|
177
|
+
},
|
|
98
178
|
docsAnchor: "app-store-connect",
|
|
99
179
|
detect: (home) => {
|
|
100
180
|
const cfg = readJson(home, "appstore.json");
|
|
@@ -103,7 +183,10 @@ var CREDENTIALS = [
|
|
|
103
183
|
},
|
|
104
184
|
{
|
|
105
185
|
id: "playstore",
|
|
106
|
-
label:
|
|
186
|
+
label: {
|
|
187
|
+
ko: "Play \uC11C\uBE44\uC2A4 \uACC4\uC815",
|
|
188
|
+
en: "Play service account"
|
|
189
|
+
},
|
|
107
190
|
// 선택이다 — helpers.ts 의 requirePlayStoreAuth 가 SA → OAuth 로 폴백하고,
|
|
108
191
|
// 로그인 시 androidpublisher 스코프를 이미 받으므로 로컬 작업은 OAuth 로 된다.
|
|
109
192
|
requirement: "optional",
|
|
@@ -112,83 +195,141 @@ var CREDENTIALS = [
|
|
|
112
195
|
fallbackOn: ["oauth"],
|
|
113
196
|
fix: "mimi-seed auth playstore",
|
|
114
197
|
setup: { kind: "mcp-bin", bin: "mimi-seed-playstore-auth" },
|
|
115
|
-
obtain:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
198
|
+
obtain: {
|
|
199
|
+
ko: [
|
|
200
|
+
"\uB85C\uCEEC\uC5D0\uC11C \uC4F8 \uAC70\uBA74 **\uD544\uC694 \uC5C6\uB2E4** \u2014 Google OAuth \uB85C \uB300\uBD80\uBD84\uC758 Play \uC791\uC5C5\uC774 \uB41C\uB2E4.",
|
|
201
|
+
"CI/\uD5E4\uB4DC\uB9AC\uC2A4(\uBE0C\uB77C\uC6B0\uC800 \uC5C6\uB294 \uD658\uACBD)\uC5D0\uC11C\uB9CC \uD544\uC694:",
|
|
202
|
+
" 1. GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
|
|
203
|
+
" 2. Play Console \u2192 \uC124\uC815 \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uAD8C\uD55C \u2192 \uADF8 \uC11C\uBE44\uC2A4 \uACC4\uC815 \uC774\uBA54\uC77C\uC744 \uCD08\uB300",
|
|
204
|
+
" 3. \uAD8C\uD55C \uC804\uD30C\uC5D0 ~5\uBD84 \uAC78\uB9B0\uB2E4. \uADF8 \uC804\uC5D4 403 \uC774 \uB728\uB294 \uAC8C \uC815\uC0C1\uC774\uB2E4.",
|
|
205
|
+
"Claude \uC5D0\uAC8C `setup_playstore_connection` \uC744 \uC2DC\uD0A4\uBA74 1\uBC88\uC744 \uC790\uB3D9\uD654\uD574\uC900\uB2E4."
|
|
206
|
+
],
|
|
207
|
+
en: [
|
|
208
|
+
"Working locally? You **don't need this** \u2014 your Google OAuth login covers most Play work.",
|
|
209
|
+
"Only needed in CI / headless environments (no browser):",
|
|
210
|
+
" 1. GCP Console \u2192 IAM & Admin \u2192 Service Accounts \u2192 Keys \u2192 Add key \u2192 Create new key \u2192 JSON",
|
|
211
|
+
" 2. Play Console \u2192 Settings \u2192 Users and permissions \u2192 invite that service account's email",
|
|
212
|
+
" 3. The grant takes ~5 min to propagate. Until then a 403 is expected, not a bug.",
|
|
213
|
+
"Ask Claude to run `setup_playstore_connection` and step 1 is done for you."
|
|
214
|
+
]
|
|
215
|
+
},
|
|
123
216
|
docsAnchor: "play-service-account",
|
|
124
|
-
note:
|
|
217
|
+
note: {
|
|
218
|
+
ko: "\uC120\uD0DD \u2014 \uB85C\uCEEC\uC740 OAuth \uB85C \uAC00\uB2A5, CI/\uD5E4\uB4DC\uB9AC\uC2A4\uB294 \uD544\uC218",
|
|
219
|
+
en: "optional \u2014 OAuth covers local; required for CI / headless"
|
|
220
|
+
},
|
|
125
221
|
detect: (home) => ({ present: hasPlaySa(home) })
|
|
126
222
|
},
|
|
127
223
|
{
|
|
128
224
|
id: "bigquery",
|
|
129
|
-
label:
|
|
225
|
+
label: {
|
|
226
|
+
ko: "BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815",
|
|
227
|
+
en: "BigQuery service account"
|
|
228
|
+
},
|
|
130
229
|
requirement: "optional",
|
|
131
230
|
group: "core",
|
|
132
231
|
file: "~/.mimi-seed/bigquery-service-account.json",
|
|
133
232
|
fallbackOn: ["oauth"],
|
|
134
233
|
fix: "mimi-seed auth bigquery",
|
|
135
234
|
setup: { kind: "mcp-bin", bin: "mimi-seed-bigquery-auth" },
|
|
136
|
-
obtain:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
235
|
+
obtain: {
|
|
236
|
+
ko: [
|
|
237
|
+
"OAuth \uB85C\uB3C4 \uB3D9\uC791\uD558\uC9C0\uB9CC, Workspace \uC7AC\uC778\uC99D \uC815\uCC45(invalid_rapt)\uC5D0 \uB9C9\uD788\uB294 \uD658\uACBD\uC774\uBA74 \uC11C\uBE44\uC2A4 \uACC4\uC815\uC774 \uD544\uC694\uD558\uB2E4.",
|
|
238
|
+
"GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
|
|
239
|
+
"\uADF8 \uC11C\uBE44\uC2A4 \uACC4\uC815\uC5D0 IAM \uC5ED\uD560\uC744 **\uC9C1\uC811** \uBD80\uC5EC\uD574\uC57C \uD55C\uB2E4 (\uC790\uB3D9\uC73C\uB85C \uC548 \uB41C\uB2E4):",
|
|
240
|
+
" \u2022 roles/bigquery.jobUser (\uCFFC\uB9AC \uC2E4\uD589)",
|
|
241
|
+
" \u2022 roles/bigquery.dataViewer (\uB370\uC774\uD130\uC14B \uC77D\uAE30)"
|
|
242
|
+
],
|
|
243
|
+
en: [
|
|
244
|
+
"OAuth works too \u2014 a service account only matters if Workspace reauth policy (invalid_rapt) keeps blocking you.",
|
|
245
|
+
"GCP Console \u2192 IAM & Admin \u2192 Service Accounts \u2192 Keys \u2192 Add key \u2192 Create new key \u2192 JSON",
|
|
246
|
+
"You must grant that service account these IAM roles **yourself** (nothing does it for you):",
|
|
247
|
+
" \u2022 roles/bigquery.jobUser (run queries)",
|
|
248
|
+
" \u2022 roles/bigquery.dataViewer (read datasets)"
|
|
249
|
+
]
|
|
250
|
+
},
|
|
143
251
|
docsAnchor: "bigquery",
|
|
144
|
-
note:
|
|
252
|
+
note: {
|
|
253
|
+
ko: "\uC120\uD0DD \u2014 OAuth \uD3F4\uBC31 \uAC00\uB2A5",
|
|
254
|
+
en: "optional \u2014 falls back to OAuth"
|
|
255
|
+
},
|
|
145
256
|
detect: (home) => ({ present: anyFileStarting(home, "bigquery") })
|
|
146
257
|
},
|
|
147
258
|
{
|
|
148
259
|
id: "github",
|
|
149
|
-
label:
|
|
260
|
+
label: {
|
|
261
|
+
ko: "GitHub Actions",
|
|
262
|
+
en: "GitHub Actions"
|
|
263
|
+
},
|
|
150
264
|
requirement: "optional",
|
|
151
265
|
group: "ci",
|
|
152
266
|
file: "~/.mimi-seed/ci.json",
|
|
153
267
|
fix: "mimi-seed auth ci",
|
|
154
268
|
setup: { kind: "cli", handler: "github" },
|
|
155
|
-
obtain:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
269
|
+
obtain: {
|
|
270
|
+
ko: [
|
|
271
|
+
"GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
|
|
272
|
+
"\u26A0\uFE0F \uC2A4\uCF54\uD504 **`repo` \uC640 `workflow` \uB97C \uB458 \uB2E4** \uCCB4\uD06C\uD574\uC57C \uD55C\uB2E4.",
|
|
273
|
+
" `workflow` \uAC00 \uC5C6\uC73C\uBA74 \uC6CC\uD06C\uD50C\uB85C dispatch \uAC00 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.",
|
|
274
|
+
"\uADF8 \uC678: owner(\uC870\uC9C1/\uC0AC\uC6A9\uC790) \xB7 repo \uC774\uB984. GitHub Enterprise \uBA74 host URL \uB3C4."
|
|
275
|
+
],
|
|
276
|
+
en: [
|
|
277
|
+
"GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
|
|
278
|
+
"\u26A0\uFE0F Check **both the `repo` and `workflow`** scopes.",
|
|
279
|
+
" Without `workflow`, dispatching a workflow fails with 403 (reading still works).",
|
|
280
|
+
"Also: owner (org/user) \xB7 repo name. Plus the host URL on GitHub Enterprise."
|
|
281
|
+
]
|
|
282
|
+
},
|
|
161
283
|
docsAnchor: "ci-github-gitlab",
|
|
162
284
|
detect: (home) => ciDetect(home, "github")
|
|
163
285
|
},
|
|
164
286
|
{
|
|
165
287
|
id: "gitlab",
|
|
166
|
-
label:
|
|
288
|
+
label: {
|
|
289
|
+
ko: "GitLab CI",
|
|
290
|
+
en: "GitLab CI"
|
|
291
|
+
},
|
|
167
292
|
requirement: "optional",
|
|
168
293
|
group: "ci",
|
|
169
294
|
file: "~/.mimi-seed/ci.json",
|
|
170
295
|
fix: "mimi-seed auth ci",
|
|
171
296
|
setup: { kind: "cli", handler: "gitlab" },
|
|
172
|
-
obtain:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
297
|
+
obtain: {
|
|
298
|
+
ko: [
|
|
299
|
+
"GitLab \u2192 \uC0AC\uC6A9\uC790 \uC124\uC815 \u2192 Access Tokens \u2192 `api` \uC2A4\uCF54\uD504\uB85C \uBC1C\uAE09 (glpat-\u2026)",
|
|
300
|
+
"\uADF8 \uC678: namespace/group \xB7 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984. self-hosted \uBA74 URL \uB3C4."
|
|
301
|
+
],
|
|
302
|
+
en: [
|
|
303
|
+
"GitLab \u2192 User settings \u2192 Access tokens \u2192 issue one with the `api` scope (glpat-\u2026)",
|
|
304
|
+
"Also: namespace/group \xB7 project name. Plus the URL if self-hosted."
|
|
305
|
+
]
|
|
306
|
+
},
|
|
176
307
|
docsAnchor: "ci-github-gitlab",
|
|
177
308
|
detect: (home) => ciDetect(home, "gitlab")
|
|
178
309
|
},
|
|
179
310
|
{
|
|
180
311
|
id: "jenkins",
|
|
181
|
-
label:
|
|
312
|
+
label: {
|
|
313
|
+
ko: "Jenkins",
|
|
314
|
+
en: "Jenkins"
|
|
315
|
+
},
|
|
182
316
|
requirement: "optional",
|
|
183
317
|
group: "ci",
|
|
184
318
|
file: "~/.mimi-seed/jenkins.json",
|
|
185
319
|
fix: "mimi-seed auth jenkins",
|
|
186
320
|
setup: { kind: "mcp-bin", bin: "mimi-seed-jenkins-auth" },
|
|
187
|
-
obtain:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
321
|
+
obtain: {
|
|
322
|
+
ko: [
|
|
323
|
+
'Jenkins \u2192 [\uC0AC\uC6A9\uC790 \uC774\uB984] \u2192 \uC124\uC815 \u2192 API Token \u2192 "Add new Token"',
|
|
324
|
+
"\uBE44\uBC00\uBC88\uD638\uAC00 \uC544\uB2C8\uB77C **API Token** \uC774\uB2E4.",
|
|
325
|
+
"\uB85C\uCEEC Jenkins \uAC00 \uC5C6\uC5B4\uB3C4 \uD68C\uC0AC/\uC6D0\uACA9 \uC11C\uBC84 URL \uC744 \uADF8\uB300\uB85C \uC4F0\uBA74 \uB41C\uB2E4."
|
|
326
|
+
],
|
|
327
|
+
en: [
|
|
328
|
+
'Jenkins \u2192 [your name] \u2192 Configure \u2192 API Token \u2192 "Add new Token"',
|
|
329
|
+
"That is an **API token**, not your password.",
|
|
330
|
+
"No local Jenkins needed \u2014 a company or remote server URL works just as well."
|
|
331
|
+
]
|
|
332
|
+
},
|
|
192
333
|
docsAnchor: "jenkins",
|
|
193
334
|
detect: (home) => {
|
|
194
335
|
const cfg = readJson(home, "jenkins.json");
|
|
@@ -197,19 +338,31 @@ var CREDENTIALS = [
|
|
|
197
338
|
},
|
|
198
339
|
{
|
|
199
340
|
id: "googleads",
|
|
200
|
-
label:
|
|
341
|
+
label: {
|
|
342
|
+
ko: "Google Ads",
|
|
343
|
+
en: "Google Ads"
|
|
344
|
+
},
|
|
201
345
|
requirement: "optional",
|
|
202
346
|
group: "marketing",
|
|
203
347
|
file: "~/.mimi-seed/google-ads.json",
|
|
204
348
|
fix: "mimi-seed auth googleads",
|
|
205
349
|
setup: { kind: "mcp-bin", bin: "mimi-seed-googleads-auth" },
|
|
206
|
-
obtain:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
350
|
+
obtain: {
|
|
351
|
+
ko: [
|
|
352
|
+
"Google Ads \u2192 \uB3C4\uAD6C \uBC0F \uC124\uC815 \u2192 \uC124\uC815 \u2192 API \uC13C\uD130 \u2192 Developer Token \uBC1C\uAE09",
|
|
353
|
+
"\u26A0\uFE0F \uCD5C\uCD08 \uBC1C\uAE09 \uD1A0\uD070\uC740 '\uD14C\uC2A4\uD2B8' \uB4F1\uAE09\uC774\uB2E4. \uC2E4\uACC4\uC815 \uB370\uC774\uD130\uB97C \uBCF4\uB824\uBA74 **\uC2B9\uC778 \uC2EC\uC0AC**\uB97C \uD1B5\uACFC\uD574\uC57C \uD558\uACE0,",
|
|
354
|
+
" \uC2EC\uC0AC\uC5D0\uB294 \uC2DC\uAC04\uC774 \uAC78\uB9B0\uB2E4 (\uC989\uC2DC \uBC1C\uAE09\uB418\uC9C0 \uC54A\uB294\uB2E4).",
|
|
355
|
+
"\uADF8 \uC678: Customer ID (\uC608: 123-456-7890). MCC \uB97C \uC4F0\uBA74 \uAD00\uB9AC\uC790 \uACC4\uC815 ID \uB3C4.",
|
|
356
|
+
"\uC778\uC99D \uC790\uCCB4\uB294 Google OAuth \uC758 `adwords` \uC2A4\uCF54\uD504\uB97C \uD0C4\uB2E4 \u2014 \uC61B \uD1A0\uD070\uC774\uBA74 \uC7AC\uB85C\uADF8\uC778\uC774 \uD544\uC694\uD560 \uC218 \uC788\uB2E4."
|
|
357
|
+
],
|
|
358
|
+
en: [
|
|
359
|
+
"Google Ads \u2192 Tools and settings \u2192 Setup \u2192 API Center \u2192 apply for a developer token",
|
|
360
|
+
"\u26A0\uFE0F The token you get immediately is 'test' tier. Reaching real account data requires Google to **approve**",
|
|
361
|
+
" your application, and that takes time (it is not instant).",
|
|
362
|
+
"Also: your Customer ID (e.g. 123-456-7890), plus the manager (MCC) account ID if you use one.",
|
|
363
|
+
"Auth itself rides on Google OAuth's `adwords` scope \u2014 an older token may need a re-login."
|
|
364
|
+
]
|
|
365
|
+
},
|
|
213
366
|
docsAnchor: "google-ads",
|
|
214
367
|
detect: (home) => {
|
|
215
368
|
const cfg = readJson(home, "google-ads.json");
|
|
@@ -218,18 +371,29 @@ var CREDENTIALS = [
|
|
|
218
371
|
},
|
|
219
372
|
{
|
|
220
373
|
id: "facebook",
|
|
221
|
-
label:
|
|
374
|
+
label: {
|
|
375
|
+
ko: "Facebook \uD398\uC774\uC9C0",
|
|
376
|
+
en: "Facebook Page"
|
|
377
|
+
},
|
|
222
378
|
requirement: "optional",
|
|
223
379
|
group: "marketing",
|
|
224
380
|
file: "~/.mimi-seed/facebook.json",
|
|
225
381
|
fix: "mimi-seed auth facebook",
|
|
226
382
|
setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["facebook"] },
|
|
227
|
-
obtain:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
383
|
+
obtain: {
|
|
384
|
+
ko: [
|
|
385
|
+
"Meta \uAC1C\uBC1C\uC790 \uC571 + \uB300\uC0C1 \uD398\uC774\uC9C0\uC758 \uAD00\uB9AC\uC790 \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
|
|
386
|
+
"Graph API Explorer \u2192 \uAD8C\uD55C pages_show_list, pages_manage_posts, pages_read_engagement",
|
|
387
|
+
"\u2192 User Token \uC0DD\uC131 \u2192 /me/accounts \uD638\uCD9C \u2192 \uADF8 \uD398\uC774\uC9C0\uC758 access_token (EAA\u2026)",
|
|
388
|
+
"long-lived \uD1A0\uD070\uC744 \uAD8C\uC7A5\uD55C\uB2E4 (\uC57D 60\uC77C)."
|
|
389
|
+
],
|
|
390
|
+
en: [
|
|
391
|
+
"You need a Meta developer app and admin rights on the target Page first.",
|
|
392
|
+
"Graph API Explorer \u2192 permissions pages_show_list, pages_manage_posts, pages_read_engagement",
|
|
393
|
+
"\u2192 generate a User token \u2192 call /me/accounts \u2192 copy that Page's access_token (EAA\u2026)",
|
|
394
|
+
"Prefer a long-lived token (~60 days)."
|
|
395
|
+
]
|
|
396
|
+
},
|
|
233
397
|
docsAnchor: "facebook",
|
|
234
398
|
detect: (home) => {
|
|
235
399
|
const cfg = readJson(home, "facebook.json");
|
|
@@ -238,18 +402,29 @@ var CREDENTIALS = [
|
|
|
238
402
|
},
|
|
239
403
|
{
|
|
240
404
|
id: "instagram",
|
|
241
|
-
label:
|
|
405
|
+
label: {
|
|
406
|
+
ko: "Instagram",
|
|
407
|
+
en: "Instagram"
|
|
408
|
+
},
|
|
242
409
|
requirement: "optional",
|
|
243
410
|
group: "marketing",
|
|
244
411
|
file: "~/.mimi-seed/instagram.json",
|
|
245
412
|
fix: "mimi-seed auth instagram",
|
|
246
413
|
setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["instagram"] },
|
|
247
|
-
obtain:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
414
|
+
obtain: {
|
|
415
|
+
ko: [
|
|
416
|
+
"long-lived \uD1A0\uD070 \uB450 \uD615\uC2DD \uBAA8\uB450 \uC9C0\uC6D0\uD55C\uB2E4 (\uC790\uB3D9 \uAC10\uC9C0):",
|
|
417
|
+
" \u2022 IGAA\u2026 \u2014 Instagram Login (Meta \uC2E0\uADDC \uBC29\uC2DD, Facebook \uD398\uC774\uC9C0 \uBD88\uD544\uC694)",
|
|
418
|
+
" \u2022 EAA\u2026 \u2014 Facebook Login (IG **\uBE44\uC988\uB2C8\uC2A4** \uACC4\uC815\uC774 FB \uD398\uC774\uC9C0\uC5D0 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uC57C \uD568)",
|
|
419
|
+
"\uD1A0\uD070 \uC218\uBA85\uC740 \uC57D 60\uC77C\uC774\uB2E4."
|
|
420
|
+
],
|
|
421
|
+
en: [
|
|
422
|
+
"Both long-lived token shapes are supported (auto-detected):",
|
|
423
|
+
" \u2022 IGAA\u2026 \u2014 Instagram Login (Meta's newer path, no Facebook Page required)",
|
|
424
|
+
" \u2022 EAA\u2026 \u2014 Facebook Login (needs an Instagram **Business** account linked to a FB Page)",
|
|
425
|
+
"Tokens last about 60 days."
|
|
426
|
+
]
|
|
427
|
+
},
|
|
253
428
|
docsAnchor: "instagram",
|
|
254
429
|
detect: (home) => {
|
|
255
430
|
const cfg = readJson(home, "instagram.json");
|
|
@@ -258,25 +433,38 @@ var CREDENTIALS = [
|
|
|
258
433
|
},
|
|
259
434
|
{
|
|
260
435
|
id: "anthropic",
|
|
261
|
-
label:
|
|
436
|
+
label: {
|
|
437
|
+
ko: "ANTHROPIC_API_KEY",
|
|
438
|
+
en: "ANTHROPIC_API_KEY"
|
|
439
|
+
},
|
|
262
440
|
requirement: "optional",
|
|
263
441
|
group: "marketing",
|
|
264
442
|
fix: "export ANTHROPIC_API_KEY=sk-ant-\u2026",
|
|
265
443
|
setup: { kind: "env", envVar: "ANTHROPIC_API_KEY" },
|
|
266
|
-
obtain:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
444
|
+
obtain: {
|
|
445
|
+
ko: [
|
|
446
|
+
"Anthropic Console (console.anthropic.com) \u2192 API Keys \uC5D0\uC11C \uBC1C\uAE09.",
|
|
447
|
+
"\uD658\uACBD\uBCC0\uC218\uB85C\uB9CC \uC77D\uB294\uB2E4 \u2014 \uC124\uC815 \uBA85\uB839\uC774 \uB530\uB85C \uC5C6\uB2E4.",
|
|
448
|
+
"\uC5C6\uC5B4\uB3C4 \uB41C\uB2E4: `mimi-seed notes` \uB294 AI \uC5C6\uC774 \uCEE4\uBC0B \uBAA9\uB85D \uAE30\uBC18\uC73C\uB85C \uB3D9\uC791\uD55C\uB2E4."
|
|
449
|
+
],
|
|
450
|
+
en: [
|
|
451
|
+
"Anthropic Console (console.anthropic.com) \u2192 API Keys.",
|
|
452
|
+
"Read from the environment only \u2014 there is no setup command.",
|
|
453
|
+
"Optional: without it, `mimi-seed notes` still works, formatting your commits instead of writing prose."
|
|
454
|
+
]
|
|
455
|
+
},
|
|
271
456
|
docsAnchor: "anthropic-api-key",
|
|
272
|
-
note:
|
|
457
|
+
note: {
|
|
458
|
+
ko: "\uC120\uD0DD \u2014 AI \uB9B4\uB9AC\uC2A4 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\uC6A9",
|
|
459
|
+
en: "optional \u2014 for AI-drafted release notes / review replies"
|
|
460
|
+
},
|
|
273
461
|
detect: () => ({ present: Boolean(process.env.ANTHROPIC_API_KEY) })
|
|
274
462
|
}
|
|
275
463
|
];
|
|
276
464
|
function tryCredById(id) {
|
|
277
465
|
return CREDENTIALS.find((c) => c.id === id);
|
|
278
466
|
}
|
|
279
|
-
function detectAll(home =
|
|
467
|
+
function detectAll(home = os2.homedir()) {
|
|
280
468
|
return new Map(CREDENTIALS.map((c) => [c.id, c.detect(home)]));
|
|
281
469
|
}
|
|
282
470
|
function isSatisfied(spec, detected) {
|
|
@@ -323,7 +511,11 @@ async function runMcpBin(bin, extraArgs = []) {
|
|
|
323
511
|
const cmd = local ? bin : "npx";
|
|
324
512
|
const args = local ? extraArgs : ["-y", MCP_PKG, bin, ...extraArgs];
|
|
325
513
|
return new Promise((resolve) => {
|
|
326
|
-
const child = spawn(cmd, args, {
|
|
514
|
+
const child = spawn(cmd, args, {
|
|
515
|
+
stdio: "inherit",
|
|
516
|
+
shell: true,
|
|
517
|
+
env: { ...process.env, MIMI_SEED_LANG: resolveLang() }
|
|
518
|
+
});
|
|
327
519
|
child.on("error", (e) => {
|
|
328
520
|
process.stderr.write(`
|
|
329
521
|
\u274C ${cmd} \uC2E4\uD589 \uC2E4\uD328: ${e.message}
|
|
@@ -335,28 +527,28 @@ async function runMcpBin(bin, extraArgs = []) {
|
|
|
335
527
|
}
|
|
336
528
|
|
|
337
529
|
// src/jenkins-config.ts
|
|
338
|
-
import
|
|
339
|
-
import
|
|
340
|
-
import
|
|
341
|
-
var CONFIG_DIR =
|
|
342
|
-
var JENKINS_PATH =
|
|
343
|
-
var LEGACY_PATH =
|
|
344
|
-
function loadJenkinsConfig(home =
|
|
530
|
+
import fs3 from "fs";
|
|
531
|
+
import os3 from "os";
|
|
532
|
+
import path3 from "path";
|
|
533
|
+
var CONFIG_DIR = path3.join(os3.homedir(), ".mimi-seed");
|
|
534
|
+
var JENKINS_PATH = path3.join(CONFIG_DIR, "jenkins.json");
|
|
535
|
+
var LEGACY_PATH = path3.join(CONFIG_DIR, "config.json");
|
|
536
|
+
function loadJenkinsConfig(home = os3.homedir()) {
|
|
345
537
|
try {
|
|
346
|
-
const p =
|
|
347
|
-
return JSON.parse(
|
|
538
|
+
const p = path3.join(home, ".mimi-seed", "jenkins.json");
|
|
539
|
+
return JSON.parse(fs3.readFileSync(p, "utf-8"));
|
|
348
540
|
} catch {
|
|
349
541
|
return null;
|
|
350
542
|
}
|
|
351
543
|
}
|
|
352
|
-
function migrateLegacyJenkins(home =
|
|
353
|
-
const dir =
|
|
354
|
-
const jenkinsPath =
|
|
355
|
-
const legacyPath =
|
|
356
|
-
if (
|
|
544
|
+
function migrateLegacyJenkins(home = os3.homedir()) {
|
|
545
|
+
const dir = path3.join(home, ".mimi-seed");
|
|
546
|
+
const jenkinsPath = path3.join(dir, "jenkins.json");
|
|
547
|
+
const legacyPath = path3.join(dir, "config.json");
|
|
548
|
+
if (fs3.existsSync(jenkinsPath)) return false;
|
|
357
549
|
let legacy;
|
|
358
550
|
try {
|
|
359
|
-
legacy = JSON.parse(
|
|
551
|
+
legacy = JSON.parse(fs3.readFileSync(legacyPath, "utf-8"));
|
|
360
552
|
} catch {
|
|
361
553
|
return false;
|
|
362
554
|
}
|
|
@@ -370,28 +562,28 @@ function migrateLegacyJenkins(home = os2.homedir()) {
|
|
|
370
562
|
...j.jobAndroid ? { jobAndroid: j.jobAndroid } : {},
|
|
371
563
|
...j.jobIos ? { jobIos: j.jobIos } : {}
|
|
372
564
|
};
|
|
373
|
-
|
|
374
|
-
|
|
565
|
+
fs3.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
566
|
+
fs3.writeFileSync(jenkinsPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
375
567
|
delete legacy.jenkins;
|
|
376
568
|
const tmp = `${legacyPath}.tmp`;
|
|
377
|
-
|
|
378
|
-
|
|
569
|
+
fs3.writeFileSync(tmp, JSON.stringify(legacy, null, 2), { mode: 384 });
|
|
570
|
+
fs3.renameSync(tmp, legacyPath);
|
|
379
571
|
return true;
|
|
380
572
|
}
|
|
381
573
|
|
|
382
574
|
// src/detect.ts
|
|
383
|
-
import
|
|
384
|
-
import
|
|
575
|
+
import fs4 from "fs/promises";
|
|
576
|
+
import path4 from "path";
|
|
385
577
|
async function readIfExists(p) {
|
|
386
578
|
try {
|
|
387
|
-
return await
|
|
579
|
+
return await fs4.readFile(p, "utf8");
|
|
388
580
|
} catch {
|
|
389
581
|
return null;
|
|
390
582
|
}
|
|
391
583
|
}
|
|
392
584
|
async function pathExists(p) {
|
|
393
585
|
try {
|
|
394
|
-
await
|
|
586
|
+
await fs4.access(p);
|
|
395
587
|
return true;
|
|
396
588
|
} catch {
|
|
397
589
|
return false;
|
|
@@ -413,16 +605,16 @@ async function walk(root, match, maxDepth = 5) {
|
|
|
413
605
|
if (depth > maxDepth) return;
|
|
414
606
|
let entries;
|
|
415
607
|
try {
|
|
416
|
-
entries = await
|
|
608
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
417
609
|
} catch {
|
|
418
610
|
return;
|
|
419
611
|
}
|
|
420
612
|
for (const e of entries) {
|
|
421
613
|
if (e.isDirectory()) {
|
|
422
614
|
if (skipDirs.has(e.name)) continue;
|
|
423
|
-
await visit(
|
|
615
|
+
await visit(path4.join(dir, e.name), depth + 1);
|
|
424
616
|
} else if (e.isFile() && match(e.name)) {
|
|
425
|
-
found.push(
|
|
617
|
+
found.push(path4.join(dir, e.name));
|
|
426
618
|
}
|
|
427
619
|
}
|
|
428
620
|
}
|
|
@@ -432,7 +624,7 @@ async function walk(root, match, maxDepth = 5) {
|
|
|
432
624
|
async function detectHints(cwd) {
|
|
433
625
|
const hints = [];
|
|
434
626
|
for (const fname of ["app.json", "app.config.json"]) {
|
|
435
|
-
const txt = await readIfExists(
|
|
627
|
+
const txt = await readIfExists(path4.join(cwd, fname));
|
|
436
628
|
if (!txt) continue;
|
|
437
629
|
try {
|
|
438
630
|
const json = JSON.parse(txt);
|
|
@@ -463,7 +655,7 @@ async function detectHints(cwd) {
|
|
|
463
655
|
if (m?.[1]) {
|
|
464
656
|
const pkg = m[1];
|
|
465
657
|
if (!hints.some((h) => h.packageName === pkg)) {
|
|
466
|
-
hints.push({ packageName: pkg, source: [
|
|
658
|
+
hints.push({ packageName: pkg, source: [path4.relative(cwd, f)] });
|
|
467
659
|
}
|
|
468
660
|
}
|
|
469
661
|
}
|
|
@@ -478,7 +670,7 @@ async function detectHints(cwd) {
|
|
|
478
670
|
let bid = m[1];
|
|
479
671
|
if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
|
|
480
672
|
if (!hints.some((h) => h.bundleId === bid)) {
|
|
481
|
-
hints.push({ bundleId: bid, source: [
|
|
673
|
+
hints.push({ bundleId: bid, source: [path4.relative(cwd, f)] });
|
|
482
674
|
}
|
|
483
675
|
}
|
|
484
676
|
}
|
|
@@ -491,11 +683,11 @@ async function detectHints(cwd) {
|
|
|
491
683
|
const bid = m[1].trim().replace(/^["']|["']$/g, "");
|
|
492
684
|
if (!bid || bid.includes("$")) continue;
|
|
493
685
|
if (!hints.some((h) => h.bundleId === bid)) {
|
|
494
|
-
hints.push({ bundleId: bid, source: [
|
|
686
|
+
hints.push({ bundleId: bid, source: [path4.relative(cwd, f)] });
|
|
495
687
|
}
|
|
496
688
|
}
|
|
497
689
|
}
|
|
498
|
-
const pkgJson = await readIfExists(
|
|
690
|
+
const pkgJson = await readIfExists(path4.join(cwd, "package.json"));
|
|
499
691
|
if (pkgJson) {
|
|
500
692
|
try {
|
|
501
693
|
const json = JSON.parse(pkgJson);
|
|
@@ -531,7 +723,7 @@ async function detectHints(cwd) {
|
|
|
531
723
|
return merged.filter((h) => h.packageName || h.bundleId);
|
|
532
724
|
}
|
|
533
725
|
async function hasAnyProjectSignal(cwd) {
|
|
534
|
-
return await pathExists(
|
|
726
|
+
return await pathExists(path4.join(cwd, "package.json")) || await pathExists(path4.join(cwd, "app.json")) || await pathExists(path4.join(cwd, "android")) || await pathExists(path4.join(cwd, "ios"));
|
|
535
727
|
}
|
|
536
728
|
|
|
537
729
|
// src/deploy.ts
|
|
@@ -539,28 +731,28 @@ import kleur from "kleur";
|
|
|
539
731
|
import * as readline from "readline";
|
|
540
732
|
|
|
541
733
|
// src/config.ts
|
|
542
|
-
import
|
|
543
|
-
import
|
|
544
|
-
import
|
|
545
|
-
var CONFIG_DIR2 =
|
|
546
|
-
var CONFIG_PATH =
|
|
734
|
+
import fs5 from "fs/promises";
|
|
735
|
+
import path5 from "path";
|
|
736
|
+
import os4 from "os";
|
|
737
|
+
var CONFIG_DIR2 = path5.join(os4.homedir(), ".mimi-seed");
|
|
738
|
+
var CONFIG_PATH = path5.join(CONFIG_DIR2, "config.json");
|
|
547
739
|
async function readConfig() {
|
|
548
740
|
try {
|
|
549
|
-
const txt = await
|
|
741
|
+
const txt = await fs5.readFile(CONFIG_PATH, "utf8");
|
|
550
742
|
return JSON.parse(txt);
|
|
551
743
|
} catch {
|
|
552
744
|
return null;
|
|
553
745
|
}
|
|
554
746
|
}
|
|
555
747
|
async function writeConfig(cfg) {
|
|
556
|
-
await
|
|
557
|
-
await
|
|
748
|
+
await fs5.mkdir(CONFIG_DIR2, { recursive: true });
|
|
749
|
+
await fs5.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
558
750
|
if (process.platform !== "win32") {
|
|
559
|
-
await
|
|
751
|
+
await fs5.chmod(CONFIG_PATH, 384);
|
|
560
752
|
}
|
|
561
753
|
}
|
|
562
754
|
async function deleteConfig() {
|
|
563
|
-
await
|
|
755
|
+
await fs5.rm(CONFIG_PATH, { force: true });
|
|
564
756
|
}
|
|
565
757
|
var CONFIG_LOCATION = CONFIG_PATH;
|
|
566
758
|
async function getEffectiveConfig() {
|
|
@@ -579,13 +771,13 @@ async function getEffectiveConfig() {
|
|
|
579
771
|
}
|
|
580
772
|
|
|
581
773
|
// src/ci-providers.ts
|
|
582
|
-
import
|
|
583
|
-
import
|
|
584
|
-
import
|
|
585
|
-
var CI_CONFIG_PATH =
|
|
774
|
+
import fs6 from "fs";
|
|
775
|
+
import path6 from "path";
|
|
776
|
+
import os5 from "os";
|
|
777
|
+
var CI_CONFIG_PATH = path6.join(os5.homedir(), ".mimi-seed", "ci.json");
|
|
586
778
|
function loadCiProviderConfig() {
|
|
587
779
|
try {
|
|
588
|
-
const cfg = JSON.parse(
|
|
780
|
+
const cfg = JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
|
|
589
781
|
const host = normalizeHost(cfg.host);
|
|
590
782
|
return host ? { ...cfg, host } : { ...cfg, host: void 0 };
|
|
591
783
|
} catch {
|
|
@@ -599,15 +791,15 @@ function normalizeHost(host) {
|
|
|
599
791
|
return withScheme.replace(/\/+$/, "");
|
|
600
792
|
}
|
|
601
793
|
function saveCiProviderConfig(cfg) {
|
|
602
|
-
const dir =
|
|
603
|
-
if (!
|
|
604
|
-
|
|
794
|
+
const dir = path6.dirname(CI_CONFIG_PATH);
|
|
795
|
+
if (!fs6.existsSync(dir)) {
|
|
796
|
+
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
605
797
|
}
|
|
606
798
|
const normalized = { ...cfg, host: normalizeHost(cfg.host) };
|
|
607
799
|
if (!normalized.host) delete normalized.host;
|
|
608
|
-
|
|
800
|
+
fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
609
801
|
if (process.platform !== "win32") {
|
|
610
|
-
|
|
802
|
+
fs6.chmodSync(CI_CONFIG_PATH, 384);
|
|
611
803
|
}
|
|
612
804
|
}
|
|
613
805
|
async function verifyCiToken(cfg) {
|
|
@@ -1125,6 +1317,210 @@ async function cmdDeploy(argv) {
|
|
|
1125
1317
|
log(kleur.dim(" https://play.google.com/console/developers"));
|
|
1126
1318
|
}
|
|
1127
1319
|
|
|
1320
|
+
// src/i18n.ts
|
|
1321
|
+
var ko = {
|
|
1322
|
+
common: {
|
|
1323
|
+
yes: "y",
|
|
1324
|
+
optional: "\uC120\uD0DD",
|
|
1325
|
+
required: "\uD544\uC218",
|
|
1326
|
+
skip: "\uAC74\uB108\uB700",
|
|
1327
|
+
cancelled: "\uCDE8\uC18C\uB428",
|
|
1328
|
+
unknownCommand: (cmd) => `\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`,
|
|
1329
|
+
error: (msg) => `\uC624\uB958: ${msg}`,
|
|
1330
|
+
checkWith: "\uC810\uAC80: mimi-seed doctor"
|
|
1331
|
+
},
|
|
1332
|
+
lang: {
|
|
1333
|
+
ask: " \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD574\uC918 [1] \uD55C\uAD6D\uC5B4 [2] English (\uC5D4\uD130=\uD55C\uAD6D\uC5B4): ",
|
|
1334
|
+
saved: (l) => ` \u2705 \uC5B8\uC5B4: ${l === "ko" ? "\uD55C\uAD6D\uC5B4" : "English"} (\uB098\uC911\uC5D0 \uBC14\uAFB8\uAE30: mimi-seed lang en)`,
|
|
1335
|
+
usage: `${"mimi-seed lang"} \u2014 CLI \uCD9C\uB825 \uC5B8\uC5B4
|
|
1336
|
+
|
|
1337
|
+
mimi-seed lang \uD604\uC7AC \uC5B8\uC5B4 \uD45C\uC2DC
|
|
1338
|
+
mimi-seed lang ko \uD55C\uAD6D\uC5B4
|
|
1339
|
+
mimi-seed lang en English
|
|
1340
|
+
|
|
1341
|
+
\uD658\uACBD\uBCC0\uC218 MIMI_SEED_LANG \uAC00 \uC788\uC73C\uBA74 \uADF8\uAC8C \uC6B0\uC120\uD569\uB2C8\uB2E4.`,
|
|
1342
|
+
current: (l) => `\uD604\uC7AC \uC5B8\uC5B4: ${l === "ko" ? "\uD55C\uAD6D\uC5B4 (ko)" : "English (en)"}`,
|
|
1343
|
+
invalid: (v) => `\uC54C \uC218 \uC5C6\uB294 \uC5B8\uC5B4: ${v} (ko \uB610\uB294 en)`
|
|
1344
|
+
},
|
|
1345
|
+
setup: {
|
|
1346
|
+
title: "mimi-seed setup",
|
|
1347
|
+
platformsDetected: (p) => ` \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${p}`,
|
|
1348
|
+
statusTitle: "\uC5F0\uACB0 \uC0C1\uD0DC",
|
|
1349
|
+
statusDir: "(~/.mimi-seed)",
|
|
1350
|
+
groupCore: "\uD575\uC2EC",
|
|
1351
|
+
groupCi: "\uBE4C\uB4DC / CI",
|
|
1352
|
+
groupMarketing: "\uB9C8\uCF00\uD305 \xB7 AI",
|
|
1353
|
+
fallbackWorking: "\uD3F4\uBC31\uC73C\uB85C \uB3D9\uC791 \uC911",
|
|
1354
|
+
missingRequired: " \uD544\uC218 \uD56D\uBAA9 \uB204\uB77D:",
|
|
1355
|
+
cannotInteract: " \u2717 \uC774 \uC790\uACA9\uC99D\uBA85\uC740 \uB300\uD654\uD615 \uC785\uB825\uC774 \uD544\uC694\uD574\uC11C \uC5EC\uAE30\uC11C\uB294 \uC124\uC815\uD560 \uC218 \uC5C6\uC5B4:",
|
|
1356
|
+
cannotInteractHint: " \uD130\uBBF8\uB110\uC5D0\uC11C \uC2E4\uD589\uD574\uC918 (Git Bash \uB4F1 TTY \uBBF8\uAC10\uC9C0 \uD658\uACBD\uC774\uBA74 --interactive).",
|
|
1357
|
+
runInTerminal: " \uB300\uD654\uD615\uC73C\uB85C \uC5F0\uACB0\uD558\uB824\uBA74 \uD130\uBBF8\uB110\uC5D0\uC11C: mimi-seed setup",
|
|
1358
|
+
onlyAlreadyDone: " \u2705 \uC694\uCCAD\uD55C \uD56D\uBAA9\uC740 \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4.",
|
|
1359
|
+
onlyReconnectHint: " \uB2E4\uC2DC \uC124\uC815\uD558\uB824\uBA74: mimi-seed setup --reconnect <id>",
|
|
1360
|
+
allDone: " \u2705 \uC5F0\uACB0\uD560 \uAC8C \uB354 \uC5C6\uC5B4. \uB2E4 \uB410\uB2E4.",
|
|
1361
|
+
planCount: (n) => ` ${n}\uAC1C \uD56D\uBAA9\uC744 \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCFC\uAC8C. \uC5B8\uC81C\uB4E0 s=\uAC74\uB108\uB6F0\uAE30, q=\uC885\uB8CC.`,
|
|
1362
|
+
prompt: " [c] \uC5F0\uACB0 [s] \uAC74\uB108\uB6F0\uAE30 [?] \uC774\uAC74 \uC5B4\uB5BB\uAC8C \uAD6C\uD558\uB098\uC694 [q] \uC885\uB8CC : ",
|
|
1363
|
+
promptInvalid: " c / s / ? / q \uC911\uC5D0\uC11C \uACE8\uB77C\uC918.",
|
|
1364
|
+
quit: " \uC911\uB2E8\uD588\uC5B4. \uC774\uC5B4\uC11C \uD558\uB824\uBA74 \uB2E4\uC2DC: mimi-seed setup",
|
|
1365
|
+
skipped: (fix) => ` \uAC74\uB108\uB700. \uB098\uC911\uC5D0: ${fix}`,
|
|
1366
|
+
obtainTitle: (label) => ` ${label} \u2014 \uBBF8\uB9AC \uC900\uBE44\uD560 \uAC83`,
|
|
1367
|
+
obtainMore: (anchor) => ` \uC790\uC138\uD788: docs/credentials.md#${anchor}`,
|
|
1368
|
+
neededFor: (platform) => `(${platform} \uBC30\uD3EC\uC5D0 \uD544\uC694)`,
|
|
1369
|
+
binFailed: (label, code, fix) => ` \u26A0 ${label} \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC5B4 (exit ${code}). \uB098\uC911\uC5D0 \uB2E4\uC2DC: ${fix}`,
|
|
1370
|
+
verifying: " \u{1F50E} \uD1A0\uD070 \uAC80\uC99D \uC911...",
|
|
1371
|
+
verifyFailed: (reason) => ` \u274C \uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328: ${reason}`,
|
|
1372
|
+
notSaved: (fix) => ` \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC5B4. \uB2E4\uC2DC: ${fix}`,
|
|
1373
|
+
ciSaved: (label, who) => ` \u2705 ${label} \uC5F0\uACB0\uB428${who} \u2192 ~/.mimi-seed/ci.json`,
|
|
1374
|
+
runSeparately: (cmd) => ` \uC774\uAC74 \uBCC4\uB3C4 \uBA85\uB839\uC73C\uB85C \uC2E4\uD589\uD574\uC918: ${cmd}`,
|
|
1375
|
+
envVar: " \uD658\uACBD\uBCC0\uC218\uB85C \uC124\uC815\uD558\uB294 \uD56D\uBAA9\uC774\uC57C:",
|
|
1376
|
+
pressEnter: " (\uC5D4\uD130\uB97C \uB204\uB974\uBA74 \uACC4\uC18D) ",
|
|
1377
|
+
stillMissing: " \uC544\uC9C1 \uD544\uC218 \uD56D\uBAA9\uC774 \uB0A8\uC544 \uC788\uC5B4:",
|
|
1378
|
+
requiredDone: " \u2705 \uD544\uC218 \uC5F0\uACB0 \uC644\uB8CC.",
|
|
1379
|
+
nextSteps: " \uC810\uAC80: mimi-seed doctor \xB7 \uBC30\uD3EC: mimi-seed deploy"
|
|
1380
|
+
},
|
|
1381
|
+
doctor: {
|
|
1382
|
+
title: "mimi-seed doctor",
|
|
1383
|
+
secAuth: "\uC778\uC99D",
|
|
1384
|
+
secCreds: "\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 (~/.mimi-seed)",
|
|
1385
|
+
secEnv: "\uB85C\uCEEC \uD658\uACBD",
|
|
1386
|
+
secApps: "\uC571 \uAC10\uC9C0",
|
|
1387
|
+
noToken: "Mimi Seed \uD1A0\uD070 \uC5C6\uC74C",
|
|
1388
|
+
noTokenFix: "`mimi-seed init` \uC2E4\uD589 \uD544\uC694",
|
|
1389
|
+
tokenSaved: "\uD1A0\uD070 \uC800\uC7A5\uB428",
|
|
1390
|
+
endpoint: "\uC5D4\uB4DC\uD3EC\uC778\uD2B8",
|
|
1391
|
+
ciMode: "CI \uBAA8\uB4DC",
|
|
1392
|
+
ciModeDetail: "MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218 \uC0AC\uC6A9 \uC911",
|
|
1393
|
+
tokenInvalid: "\uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328",
|
|
1394
|
+
serverOk: "Mimi Seed \uC11C\uBC84 \uC5F0\uACB0\uB428",
|
|
1395
|
+
appCount: (n) => `\uC571 ${n}\uAC1C`,
|
|
1396
|
+
unknownService: (id) => `${id} (\uC54C \uC218 \uC5C6\uB294 \uC11C\uBE44\uC2A4)`,
|
|
1397
|
+
credsHint: " \uC804\uBD80 \uC5F0\uACB0\uD558\uAE30: mimi-seed setup \xB7 OAuth \uC2E0\uC120\uB3C4: mimi-seed auth status\n",
|
|
1398
|
+
nodeTooOld: (v) => `${v} \u2014 v20 \uC774\uC0C1 \uD544\uC694 (.nvmrc \uCC38\uACE0)`,
|
|
1399
|
+
gitRepo: "Git \uC800\uC7A5\uC18C",
|
|
1400
|
+
gitTag: (t2) => `\uCD5C\uC2E0 \uD0DC\uADF8: ${t2}`,
|
|
1401
|
+
gitCommits: (n) => `\uCEE4\uBC0B ${n}\uAC1C`,
|
|
1402
|
+
noGit: "Git \uC800\uC7A5\uC18C \uC5C6\uC74C",
|
|
1403
|
+
noGitDetail: "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00",
|
|
1404
|
+
noApp: "\uC571 \uAC10\uC9C0 \uC5C6\uC74C",
|
|
1405
|
+
noAppDetail: "app.json / build.gradle / Info.plist \uC5C6\uC74C",
|
|
1406
|
+
unnamed: "(\uC774\uB984 \uBBF8\uC0C1)",
|
|
1407
|
+
requirements: (proj) => `${proj} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`,
|
|
1408
|
+
thisProject: "\uC774 \uD504\uB85C\uC81D\uD2B8"
|
|
1409
|
+
},
|
|
1410
|
+
auth: {
|
|
1411
|
+
title: "mimi-seed auth \u2014 \uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC778\uC99D/\uAD00\uB9AC",
|
|
1412
|
+
statusTitle: "\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC0C1\uD0DC",
|
|
1413
|
+
connectAll: "\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup",
|
|
1414
|
+
unknownSub: (sub) => `\uC54C \uC218 \uC5C6\uB294 auth \uC11C\uBE0C\uBA85\uB839: ${sub}`,
|
|
1415
|
+
npxFailed: (cmd, msg) => `
|
|
1416
|
+
\u274C ${cmd} \uC2E4\uD589 \uC2E4\uD328: ${msg}
|
|
1417
|
+
`
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
var en = {
|
|
1421
|
+
common: {
|
|
1422
|
+
yes: "y",
|
|
1423
|
+
optional: "optional",
|
|
1424
|
+
required: "required",
|
|
1425
|
+
skip: "skipped",
|
|
1426
|
+
cancelled: "Cancelled",
|
|
1427
|
+
unknownCommand: (cmd) => `Unknown command: ${cmd}`,
|
|
1428
|
+
error: (msg) => `Error: ${msg}`,
|
|
1429
|
+
checkWith: "Check with: mimi-seed doctor"
|
|
1430
|
+
},
|
|
1431
|
+
lang: {
|
|
1432
|
+
ask: " Choose a language [1] \uD55C\uAD6D\uC5B4 [2] English (Enter = \uD55C\uAD6D\uC5B4): ",
|
|
1433
|
+
saved: (l) => ` \u2705 Language: ${l === "ko" ? "\uD55C\uAD6D\uC5B4" : "English"} (change later: mimi-seed lang ko)`,
|
|
1434
|
+
usage: `mimi-seed lang \u2014 CLI output language
|
|
1435
|
+
|
|
1436
|
+
mimi-seed lang show current language
|
|
1437
|
+
mimi-seed lang ko \uD55C\uAD6D\uC5B4
|
|
1438
|
+
mimi-seed lang en English
|
|
1439
|
+
|
|
1440
|
+
MIMI_SEED_LANG takes precedence when set.`,
|
|
1441
|
+
current: (l) => `Current language: ${l === "ko" ? "\uD55C\uAD6D\uC5B4 (ko)" : "English (en)"}`,
|
|
1442
|
+
invalid: (v) => `Unknown language: ${v} (use ko or en)`
|
|
1443
|
+
},
|
|
1444
|
+
setup: {
|
|
1445
|
+
title: "mimi-seed setup",
|
|
1446
|
+
platformsDetected: (p) => ` Detected platforms: ${p}`,
|
|
1447
|
+
statusTitle: "Connection status",
|
|
1448
|
+
statusDir: "(~/.mimi-seed)",
|
|
1449
|
+
groupCore: "Core",
|
|
1450
|
+
groupCi: "Build / CI",
|
|
1451
|
+
groupMarketing: "Marketing \xB7 AI",
|
|
1452
|
+
fallbackWorking: "working via fallback",
|
|
1453
|
+
missingRequired: " Missing required:",
|
|
1454
|
+
cannotInteract: " \u2717 These need interactive input and cannot be set up here:",
|
|
1455
|
+
cannotInteractHint: " Run it in a terminal (add --interactive if your shell hides the TTY, e.g. Git Bash).",
|
|
1456
|
+
runInTerminal: " To connect interactively, run: mimi-seed setup",
|
|
1457
|
+
onlyAlreadyDone: " \u2705 What you asked for is already connected.",
|
|
1458
|
+
onlyReconnectHint: " To redo it: mimi-seed setup --reconnect <id>",
|
|
1459
|
+
allDone: " \u2705 Nothing left to connect. You're set.",
|
|
1460
|
+
planCount: (n) => ` I'll walk you through ${n} item(s). s = skip, q = quit, anytime.`,
|
|
1461
|
+
prompt: " [c] connect [s] skip [?] how do I get this [q] quit : ",
|
|
1462
|
+
promptInvalid: " Please choose c / s / ? / q.",
|
|
1463
|
+
quit: " Stopped. To pick up where you left off: mimi-seed setup",
|
|
1464
|
+
skipped: (fix) => ` Skipped. Later: ${fix}`,
|
|
1465
|
+
obtainTitle: (label) => ` ${label} \u2014 what to get first`,
|
|
1466
|
+
obtainMore: (anchor) => ` Details: docs/credentials.md#${anchor}`,
|
|
1467
|
+
neededFor: (platform) => `(needed to ship to ${platform})`,
|
|
1468
|
+
binFailed: (label, code, fix) => ` \u26A0 ${label} was not completed (exit ${code}). Try again later: ${fix}`,
|
|
1469
|
+
verifying: " \u{1F50E} Verifying token...",
|
|
1470
|
+
verifyFailed: (reason) => ` \u274C Token verification failed: ${reason}`,
|
|
1471
|
+
notSaved: (fix) => ` Nothing was saved. Retry: ${fix}`,
|
|
1472
|
+
ciSaved: (label, who) => ` \u2705 ${label} connected${who} \u2192 ~/.mimi-seed/ci.json`,
|
|
1473
|
+
runSeparately: (cmd) => ` Run this one separately: ${cmd}`,
|
|
1474
|
+
envVar: " This one is set through an environment variable:",
|
|
1475
|
+
pressEnter: " (press Enter to continue) ",
|
|
1476
|
+
stillMissing: " Still missing, and required:",
|
|
1477
|
+
requiredDone: " \u2705 All required credentials connected.",
|
|
1478
|
+
nextSteps: " Check: mimi-seed doctor \xB7 Ship: mimi-seed deploy"
|
|
1479
|
+
},
|
|
1480
|
+
doctor: {
|
|
1481
|
+
title: "mimi-seed doctor",
|
|
1482
|
+
secAuth: "Account",
|
|
1483
|
+
secCreds: "Local credentials (~/.mimi-seed)",
|
|
1484
|
+
secEnv: "Environment",
|
|
1485
|
+
secApps: "App detection",
|
|
1486
|
+
noToken: "No Mimi Seed token",
|
|
1487
|
+
noTokenFix: "run `mimi-seed init`",
|
|
1488
|
+
tokenSaved: "Token stored",
|
|
1489
|
+
endpoint: "Endpoint",
|
|
1490
|
+
ciMode: "CI mode",
|
|
1491
|
+
ciModeDetail: "using MIMI_SEED_TOKEN",
|
|
1492
|
+
tokenInvalid: "Token rejected",
|
|
1493
|
+
serverOk: "Connected to Mimi Seed",
|
|
1494
|
+
appCount: (n) => `${n} app(s)`,
|
|
1495
|
+
unknownService: (id) => `${id} (unknown service)`,
|
|
1496
|
+
credsHint: " Connect everything: mimi-seed setup \xB7 OAuth freshness: mimi-seed auth status\n",
|
|
1497
|
+
nodeTooOld: (v) => `${v} \u2014 v20+ required (see .nvmrc)`,
|
|
1498
|
+
gitRepo: "Git repository",
|
|
1499
|
+
gitTag: (t2) => `latest tag: ${t2}`,
|
|
1500
|
+
gitCommits: (n) => `${n} commit(s)`,
|
|
1501
|
+
noGit: "Not a git repository",
|
|
1502
|
+
noGitDetail: "mimi-seed notes is unavailable",
|
|
1503
|
+
noApp: "No app detected",
|
|
1504
|
+
noAppDetail: "no app.json / build.gradle / Info.plist",
|
|
1505
|
+
unnamed: "(unnamed)",
|
|
1506
|
+
requirements: (proj) => `${proj} requirements (.mimi-seed.json)`,
|
|
1507
|
+
thisProject: "This project"
|
|
1508
|
+
},
|
|
1509
|
+
auth: {
|
|
1510
|
+
title: "mimi-seed auth \u2014 local credential setup",
|
|
1511
|
+
statusTitle: "Local credential status",
|
|
1512
|
+
connectAll: "\n Connect everything at once: mimi-seed setup",
|
|
1513
|
+
unknownSub: (sub) => `Unknown auth subcommand: ${sub}`,
|
|
1514
|
+
npxFailed: (cmd, msg) => `
|
|
1515
|
+
\u274C Failed to run ${cmd}: ${msg}
|
|
1516
|
+
`
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
var CATALOGS = { ko, en };
|
|
1520
|
+
function t() {
|
|
1521
|
+
return CATALOGS[resolveLang()];
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1128
1524
|
// src/setup.ts
|
|
1129
1525
|
function log2(msg = "") {
|
|
1130
1526
|
process.stdout.write(msg + "\n");
|
|
@@ -1169,15 +1565,16 @@ function statusLine(spec, detected, platforms) {
|
|
|
1169
1565
|
else if (isSatisfied(spec, detected)) mark = kleur2.yellow("~");
|
|
1170
1566
|
else if (spec.requirement === "optional" || !relevant) mark = kleur2.dim("\xB7");
|
|
1171
1567
|
else mark = kleur2.red("\u2717");
|
|
1172
|
-
const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${spec
|
|
1173
|
-
return ` ${mark} ${spec.
|
|
1568
|
+
const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${credNote(spec) ?? t().setup.fallbackWorking}`) : kleur2.dim(`\u2192 ${spec.fix}`);
|
|
1569
|
+
return ` ${mark} ${credLabel(spec).padEnd(24)} ${tail}`;
|
|
1174
1570
|
}
|
|
1175
1571
|
function printStatus(detected, platforms) {
|
|
1176
|
-
|
|
1572
|
+
const m = t().setup;
|
|
1573
|
+
log2(kleur2.bold(m.statusTitle + " ") + kleur2.dim(m.statusDir));
|
|
1177
1574
|
log2();
|
|
1178
1575
|
for (const group of ["core", "ci", "marketing"]) {
|
|
1179
1576
|
const specs = CREDENTIALS.filter((c) => c.group === group);
|
|
1180
|
-
const title = group === "core" ?
|
|
1577
|
+
const title = group === "core" ? m.groupCore : group === "ci" ? m.groupCi : m.groupMarketing;
|
|
1181
1578
|
log2(kleur2.dim(` \u2500\u2500 ${title} \u2500\u2500`));
|
|
1182
1579
|
for (const spec of specs) log2(statusLine(spec, detected, platforms));
|
|
1183
1580
|
log2();
|
|
@@ -1186,70 +1583,86 @@ function printStatus(detected, platforms) {
|
|
|
1186
1583
|
function ask(q) {
|
|
1187
1584
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
1188
1585
|
return new Promise((resolve) => {
|
|
1189
|
-
|
|
1586
|
+
let done = false;
|
|
1587
|
+
const finish = (value) => {
|
|
1588
|
+
if (done) return;
|
|
1589
|
+
done = true;
|
|
1190
1590
|
rl.close();
|
|
1191
|
-
resolve(
|
|
1192
|
-
}
|
|
1193
|
-
rl.
|
|
1591
|
+
resolve(value);
|
|
1592
|
+
};
|
|
1593
|
+
rl.question(q, (a) => finish(a.trim()));
|
|
1594
|
+
rl.on("close", () => finish(""));
|
|
1194
1595
|
});
|
|
1195
1596
|
}
|
|
1196
1597
|
function printObtain(spec) {
|
|
1197
1598
|
log2();
|
|
1198
|
-
log2(kleur2.bold(
|
|
1199
|
-
for (const line of spec
|
|
1599
|
+
log2(kleur2.bold(t().setup.obtainTitle(credLabel(spec))));
|
|
1600
|
+
for (const line of credObtain(spec)) log2(kleur2.dim(` ${line}`));
|
|
1200
1601
|
if (spec.docsAnchor) {
|
|
1201
|
-
log2(kleur2.dim(
|
|
1602
|
+
log2(kleur2.dim(t().setup.obtainMore(spec.docsAnchor)));
|
|
1202
1603
|
}
|
|
1203
1604
|
log2();
|
|
1204
1605
|
}
|
|
1606
|
+
async function ensureLangChosen() {
|
|
1607
|
+
if (!isLangUnset()) return;
|
|
1608
|
+
const answer = await ask(t().lang.ask);
|
|
1609
|
+
const lang = answer.trim() === "2" || answer.trim().toLowerCase() === "en" ? "en" : "ko";
|
|
1610
|
+
writeSettings({ lang });
|
|
1611
|
+
process.env.MIMI_SEED_LANG = lang;
|
|
1612
|
+
log2(kleur2.green(t().lang.saved(lang)));
|
|
1613
|
+
log2();
|
|
1614
|
+
}
|
|
1205
1615
|
async function connectOne(spec) {
|
|
1206
1616
|
switch (spec.setup.kind) {
|
|
1207
1617
|
case "mcp-bin": {
|
|
1208
1618
|
const code = await runMcpBin(spec.setup.bin, spec.setup.args ?? []);
|
|
1209
1619
|
if (code !== 0) {
|
|
1210
|
-
log2(kleur2.yellow(
|
|
1620
|
+
log2(kleur2.yellow(t().setup.binFailed(credLabel(spec), code, spec.fix)));
|
|
1211
1621
|
}
|
|
1212
1622
|
return true;
|
|
1213
1623
|
}
|
|
1214
1624
|
case "cli": {
|
|
1215
1625
|
const provider = spec.setup.handler;
|
|
1216
1626
|
const cfg = await promptGitProviderSetup(provider);
|
|
1217
|
-
log2(kleur2.dim(
|
|
1627
|
+
log2(kleur2.dim(t().setup.verifying));
|
|
1218
1628
|
const check = await verifyCiToken(cfg);
|
|
1219
1629
|
if (!check.ok) {
|
|
1220
|
-
log2(kleur2.red(
|
|
1221
|
-
log2(kleur2.dim(
|
|
1630
|
+
log2(kleur2.red(t().setup.verifyFailed(check.reason ?? "")));
|
|
1631
|
+
log2(kleur2.dim(t().setup.notSaved(spec.fix)));
|
|
1222
1632
|
return true;
|
|
1223
1633
|
}
|
|
1224
1634
|
saveCiProviderConfig(cfg);
|
|
1225
|
-
log2(kleur2.green(
|
|
1635
|
+
log2(kleur2.green(t().setup.ciSaved(credLabel(spec), check.login ? ` (${check.login})` : "")));
|
|
1226
1636
|
return true;
|
|
1227
1637
|
}
|
|
1228
1638
|
case "command": {
|
|
1229
|
-
log2(kleur2.yellow(
|
|
1230
|
-
await ask(kleur2.dim(
|
|
1639
|
+
log2(kleur2.yellow(t().setup.runSeparately(kleur2.cyan(spec.setup.run))));
|
|
1640
|
+
await ask(kleur2.dim(t().setup.pressEnter));
|
|
1231
1641
|
return true;
|
|
1232
1642
|
}
|
|
1233
1643
|
case "env": {
|
|
1234
|
-
log2(kleur2.yellow(
|
|
1644
|
+
log2(kleur2.yellow(t().setup.envVar));
|
|
1235
1645
|
log2(kleur2.cyan(` export ${spec.setup.envVar}=...`));
|
|
1236
1646
|
log2(kleur2.dim(` (Windows PowerShell: $env:${spec.setup.envVar} = "..." )`));
|
|
1237
|
-
await ask(kleur2.dim(
|
|
1647
|
+
await ask(kleur2.dim(t().setup.pressEnter));
|
|
1238
1648
|
return true;
|
|
1239
1649
|
}
|
|
1240
1650
|
}
|
|
1241
1651
|
}
|
|
1242
1652
|
async function cmdSetup(argv) {
|
|
1243
1653
|
const opts = parseSetupArgs(argv);
|
|
1244
|
-
const home =
|
|
1654
|
+
const home = os6.homedir();
|
|
1245
1655
|
migrateLegacyJenkins(home);
|
|
1656
|
+
if (resolveMode(opts, process.env, process.stdin.isTTY) === "interactive") {
|
|
1657
|
+
await ensureLangChosen();
|
|
1658
|
+
}
|
|
1246
1659
|
const hints = await detectHints(process.cwd());
|
|
1247
1660
|
const platforms = opts.platforms ?? [
|
|
1248
1661
|
hints.some((h) => h.packageName) ? "android" : null,
|
|
1249
1662
|
hints.some((h) => h.bundleId) ? "ios" : null
|
|
1250
1663
|
].filter(Boolean);
|
|
1251
|
-
log2(kleur2.bold(
|
|
1252
|
-
if (platforms.length > 0) log2(kleur2.dim(
|
|
1664
|
+
log2(kleur2.bold(t().setup.title));
|
|
1665
|
+
if (platforms.length > 0) log2(kleur2.dim(t().setup.platformsDetected(platforms.join(", "))));
|
|
1253
1666
|
log2();
|
|
1254
1667
|
let detected = detectAll(home);
|
|
1255
1668
|
printStatus(detected, platforms);
|
|
@@ -1262,53 +1675,54 @@ async function cmdSetup(argv) {
|
|
|
1262
1675
|
if (mode === "report-only") {
|
|
1263
1676
|
const missing = missingRequired(detected, platforms);
|
|
1264
1677
|
if (missing.length > 0) {
|
|
1265
|
-
log2(kleur2.yellow(
|
|
1266
|
-
for (const spec of missing) log2(kleur2.yellow(` \u2022 ${spec
|
|
1678
|
+
log2(kleur2.yellow(t().setup.missingRequired));
|
|
1679
|
+
for (const spec of missing) log2(kleur2.yellow(` \u2022 ${credLabel(spec)} \u2192 ${spec.fix}`));
|
|
1267
1680
|
log2();
|
|
1268
1681
|
}
|
|
1269
1682
|
if (opts.only && plan.length > 0) {
|
|
1270
|
-
log2(kleur2.red(
|
|
1271
|
-
for (const spec of plan) log2(kleur2.red(` \u2022 ${spec
|
|
1272
|
-
log2(kleur2.dim(
|
|
1683
|
+
log2(kleur2.red(t().setup.cannotInteract));
|
|
1684
|
+
for (const spec of plan) log2(kleur2.red(` \u2022 ${credLabel(spec)}`));
|
|
1685
|
+
log2(kleur2.dim(t().setup.cannotInteractHint));
|
|
1273
1686
|
process.exit(1);
|
|
1274
1687
|
}
|
|
1275
|
-
log2(kleur2.dim(
|
|
1688
|
+
log2(kleur2.dim(t().setup.runInTerminal));
|
|
1276
1689
|
if (opts.failOnMissing && missing.length > 0) process.exit(1);
|
|
1277
1690
|
return;
|
|
1278
1691
|
}
|
|
1279
1692
|
if (plan.length === 0) {
|
|
1280
1693
|
if (opts.only) {
|
|
1281
|
-
log2(kleur2.green(
|
|
1282
|
-
log2(kleur2.dim(
|
|
1694
|
+
log2(kleur2.green(t().setup.onlyAlreadyDone));
|
|
1695
|
+
log2(kleur2.dim(t().setup.onlyReconnectHint));
|
|
1283
1696
|
} else {
|
|
1284
|
-
log2(kleur2.green(
|
|
1697
|
+
log2(kleur2.green(t().setup.allDone));
|
|
1285
1698
|
}
|
|
1286
|
-
log2(kleur2.dim("
|
|
1699
|
+
log2(kleur2.dim(" " + t().common.checkWith));
|
|
1287
1700
|
if (opts.failOnMissing && missingRequired(detected, platforms).length > 0) process.exit(1);
|
|
1288
1701
|
return;
|
|
1289
1702
|
}
|
|
1290
|
-
log2(kleur2.dim(
|
|
1703
|
+
log2(kleur2.dim(t().setup.planCount(plan.length)));
|
|
1291
1704
|
log2();
|
|
1292
1705
|
let aborted = false;
|
|
1293
1706
|
for (const spec of plan) {
|
|
1294
1707
|
if (aborted) break;
|
|
1295
|
-
const req = spec.requirement === "optional" ? kleur2.dim(
|
|
1708
|
+
const req = spec.requirement === "optional" ? kleur2.dim(`(${t().common.optional})`) : spec.requirement === "platform" ? kleur2.dim(t().setup.neededFor(spec.platform)) : kleur2.red(`(${t().common.required})`);
|
|
1296
1709
|
for (; ; ) {
|
|
1297
|
-
log2(kleur2.bold(`\u25B8 ${spec
|
|
1298
|
-
|
|
1299
|
-
|
|
1710
|
+
log2(kleur2.bold(`\u25B8 ${credLabel(spec)} ${req}`));
|
|
1711
|
+
const note = credNote(spec);
|
|
1712
|
+
if (note) log2(kleur2.dim(` ${note}`));
|
|
1713
|
+
const answer = (await ask(t().setup.prompt)).toLowerCase();
|
|
1300
1714
|
if (answer === "?" || answer === "h") {
|
|
1301
1715
|
printObtain(spec);
|
|
1302
1716
|
continue;
|
|
1303
1717
|
}
|
|
1304
1718
|
if (answer === "q") {
|
|
1305
1719
|
log2();
|
|
1306
|
-
log2(kleur2.dim(
|
|
1720
|
+
log2(kleur2.dim(t().setup.quit));
|
|
1307
1721
|
aborted = true;
|
|
1308
1722
|
break;
|
|
1309
1723
|
}
|
|
1310
1724
|
if (answer === "s" || answer === "") {
|
|
1311
|
-
log2(kleur2.dim(
|
|
1725
|
+
log2(kleur2.dim(t().setup.skipped(spec.fix)));
|
|
1312
1726
|
log2();
|
|
1313
1727
|
break;
|
|
1314
1728
|
}
|
|
@@ -1317,7 +1731,7 @@ async function cmdSetup(argv) {
|
|
|
1317
1731
|
log2();
|
|
1318
1732
|
break;
|
|
1319
1733
|
}
|
|
1320
|
-
log2(kleur2.dim(
|
|
1734
|
+
log2(kleur2.dim(t().setup.promptInvalid));
|
|
1321
1735
|
}
|
|
1322
1736
|
}
|
|
1323
1737
|
detected = detectAll(home);
|
|
@@ -1325,12 +1739,12 @@ async function cmdSetup(argv) {
|
|
|
1325
1739
|
printStatus(detected, platforms);
|
|
1326
1740
|
const stillMissing = missingRequired(detected, platforms);
|
|
1327
1741
|
if (stillMissing.length === 0) {
|
|
1328
|
-
log2(kleur2.green(
|
|
1742
|
+
log2(kleur2.green(t().setup.requiredDone));
|
|
1329
1743
|
} else {
|
|
1330
|
-
log2(kleur2.yellow(
|
|
1331
|
-
for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${spec
|
|
1744
|
+
log2(kleur2.yellow(t().setup.stillMissing));
|
|
1745
|
+
for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${credLabel(spec)} \u2192 ${spec.fix}`));
|
|
1332
1746
|
}
|
|
1333
|
-
log2(kleur2.dim(
|
|
1747
|
+
log2(kleur2.dim(t().setup.nextSteps));
|
|
1334
1748
|
log2();
|
|
1335
1749
|
if (opts.failOnMissing && stillMissing.length > 0) process.exit(1);
|
|
1336
1750
|
}
|
|
@@ -1342,11 +1756,17 @@ export {
|
|
|
1342
1756
|
deleteConfig,
|
|
1343
1757
|
CONFIG_LOCATION,
|
|
1344
1758
|
getEffectiveConfig,
|
|
1759
|
+
writeSettings,
|
|
1760
|
+
isLang,
|
|
1761
|
+
resolveLang,
|
|
1762
|
+
credLabel,
|
|
1763
|
+
credNote,
|
|
1345
1764
|
CREDENTIALS,
|
|
1346
1765
|
tryCredById,
|
|
1347
1766
|
detectAll,
|
|
1348
1767
|
isSatisfied,
|
|
1349
1768
|
migrateLegacyJenkins,
|
|
1769
|
+
t,
|
|
1350
1770
|
MCP_PKG,
|
|
1351
1771
|
runMcpBin,
|
|
1352
1772
|
cmdDeploy,
|