mimi-seed 0.5.0 → 0.7.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.
@@ -1,4 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ catalog,
4
+ isLangUnset,
5
+ resolveLang,
6
+ t,
7
+ writeSettings
8
+ } from "./chunk-SZQFVTEW.js";
2
9
 
3
10
  // src/setup.ts
4
11
  import kleur2 from "kleur";
@@ -9,6 +16,15 @@ import os5 from "os";
9
16
  import fs from "fs";
10
17
  import os from "os";
11
18
  import path from "path";
19
+ function credLabel(spec, lang = resolveLang()) {
20
+ return spec.label[lang];
21
+ }
22
+ function credNote(spec, lang = resolveLang()) {
23
+ return spec.note?.[lang];
24
+ }
25
+ function credObtain(spec, lang = resolveLang()) {
26
+ return spec.obtain[lang];
27
+ }
12
28
  function credDir(home) {
13
29
  return path.join(home, ".mimi-seed");
14
30
  }
@@ -45,16 +61,25 @@ function ciDetect(home, want) {
45
61
  var CREDENTIALS = [
46
62
  {
47
63
  id: "mimiseed",
48
- label: "Mimi Seed \uACC4\uC815 (\uD074\uB77C\uC6B0\uB4DC)",
64
+ label: {
65
+ ko: "Mimi Seed \uACC4\uC815 (\uD074\uB77C\uC6B0\uB4DC)",
66
+ en: "Mimi Seed account (cloud)"
67
+ },
49
68
  requirement: "required",
50
69
  group: "core",
51
70
  file: "~/.mimi-seed/config.json",
52
71
  fix: "mimi-seed init",
53
72
  setup: { kind: "command", run: "mimi-seed init" },
54
- obtain: [
55
- "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.",
56
- "CI \uC5D0\uC11C\uB294 MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218\uB85C \uB300\uCCB4\uD560 \uC218 \uC788\uB2E4."
57
- ],
73
+ obtain: {
74
+ ko: [
75
+ "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.",
76
+ "CI \uC5D0\uC11C\uB294 MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218\uB85C \uB300\uCCB4\uD560 \uC218 \uC788\uB2E4."
77
+ ],
78
+ en: [
79
+ "Sign up / sign in at https://mimi-seed.pryzm.gg \u2014 the browser hands the token back for you.",
80
+ "In CI, set the MIMI_SEED_TOKEN environment variable instead."
81
+ ]
82
+ },
58
83
  docsAnchor: "cloud-pat",
59
84
  detect: (home) => {
60
85
  if (process.env.MIMI_SEED_TOKEN) return { present: true, detail: "MIMI_SEED_TOKEN (env)" };
@@ -64,37 +89,62 @@ var CREDENTIALS = [
64
89
  },
65
90
  {
66
91
  id: "oauth",
67
- label: "Google OAuth",
92
+ label: {
93
+ ko: "Google OAuth",
94
+ en: "Google OAuth"
95
+ },
68
96
  requirement: "required",
69
97
  group: "core",
70
98
  file: "~/.mimi-seed/tokens.json",
71
99
  fix: "mimi-seed auth login",
72
100
  setup: { kind: "mcp-bin", bin: "mimi-seed-auth" },
73
- obtain: [
74
- "\uBBF8\uB9AC \uBC1C\uAE09\uBC1B\uC744 \uAC8C **\uC5C6\uB2E4**. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778 \uD55C \uBC88\uC774\uBA74 \uB05D.",
75
- "\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.",
76
- "\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",
77
- " \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."
78
- ],
101
+ obtain: {
102
+ ko: [
103
+ "\uBBF8\uB9AC \uBC1C\uAE09\uBC1B\uC744 \uAC8C **\uC5C6\uB2E4**. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778 \uD55C \uBC88\uC774\uBA74 \uB05D.",
104
+ "\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.",
105
+ "\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",
106
+ " \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."
107
+ ],
108
+ en: [
109
+ "**Nothing** to fetch beforehand. One browser sign-in is all it takes.",
110
+ "This single token unlocks Firebase \xB7 AdMob \xB7 Play \xB7 Google Ads \xB7 Search Console \xB7 GA4 \xB7 IAM \xB7 BigQuery.",
111
+ "\u26A0\uFE0F If the login screen blocks you (access_denied), the app is still in 'testing' mode \u2014",
112
+ " ask whoever operates it to add your Google account under Test users. Retrying alone never works."
113
+ ]
114
+ },
79
115
  docsAnchor: "google-oauth",
80
- note: "8\uAC1C Google \uC11C\uBE44\uC2A4\uC758 \uACF5\uD1B5 \uAD00\uBB38",
116
+ note: {
117
+ ko: "8\uAC1C Google \uC11C\uBE44\uC2A4\uC758 \uACF5\uD1B5 \uAD00\uBB38",
118
+ en: "the shared gateway to 8 Google services"
119
+ },
81
120
  detect: (home) => ({ present: hasFile(home, "tokens.json") })
82
121
  },
83
122
  {
84
123
  id: "appstore",
85
- label: "App Store Connect",
124
+ label: {
125
+ ko: "App Store Connect",
126
+ en: "App Store Connect"
127
+ },
86
128
  requirement: "platform",
87
129
  platform: "ios",
88
130
  group: "core",
89
131
  file: "~/.mimi-seed/appstore.json",
90
132
  fix: "mimi-seed auth appstore",
91
133
  setup: { kind: "mcp-bin", bin: "mimi-seed-appstore-auth" },
92
- obtain: [
93
- "\uC720\uB8CC Apple Developer Program \uBA64\uBC84\uC2ED + Admin/App Manager \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
94
- "App Store Connect \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uC561\uC138\uC2A4 \u2192 \uD1B5\uD569 \u2192 App Store Connect API \u2192 \uD0A4 \uC0DD\uC131",
95
- "\uAC00\uC838\uC62C \uAC83 3\uAC1C: Issuer ID \xB7 Key ID \xB7 .p8 \uD30C\uC77C",
96
- "\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."
97
- ],
134
+ obtain: {
135
+ ko: [
136
+ "\uC720\uB8CC Apple Developer Program \uBA64\uBC84\uC2ED + Admin/App Manager \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
137
+ "App Store Connect \u2192 \uC0AC\uC6A9\uC790 \uBC0F \uC561\uC138\uC2A4 \u2192 \uD1B5\uD569 \u2192 App Store Connect API \u2192 \uD0A4 \uC0DD\uC131",
138
+ "\uAC00\uC838\uC62C \uAC83 3\uAC1C: Issuer ID \xB7 Key ID \xB7 .p8 \uD30C\uC77C",
139
+ "\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."
140
+ ],
141
+ en: [
142
+ "You need a **paid** Apple Developer Program membership and the Admin / App Manager role first.",
143
+ "App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 App Store Connect API \u2192 generate a key",
144
+ "Collect three things: Issuer ID \xB7 Key ID \xB7 the .p8 file",
145
+ "\u26A0\uFE0F The .p8 downloads **exactly once**. Lose it and you must revoke the key and issue a new one."
146
+ ]
147
+ },
98
148
  docsAnchor: "app-store-connect",
99
149
  detect: (home) => {
100
150
  const cfg = readJson(home, "appstore.json");
@@ -103,7 +153,10 @@ var CREDENTIALS = [
103
153
  },
104
154
  {
105
155
  id: "playstore",
106
- label: "Play \uC11C\uBE44\uC2A4 \uACC4\uC815",
156
+ label: {
157
+ ko: "Play \uC11C\uBE44\uC2A4 \uACC4\uC815",
158
+ en: "Play service account"
159
+ },
107
160
  // 선택이다 — helpers.ts 의 requirePlayStoreAuth 가 SA → OAuth 로 폴백하고,
108
161
  // 로그인 시 androidpublisher 스코프를 이미 받으므로 로컬 작업은 OAuth 로 된다.
109
162
  requirement: "optional",
@@ -112,83 +165,141 @@ var CREDENTIALS = [
112
165
  fallbackOn: ["oauth"],
113
166
  fix: "mimi-seed auth playstore",
114
167
  setup: { kind: "mcp-bin", bin: "mimi-seed-playstore-auth" },
115
- obtain: [
116
- "\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.",
117
- "CI/\uD5E4\uB4DC\uB9AC\uC2A4(\uBE0C\uB77C\uC6B0\uC800 \uC5C6\uB294 \uD658\uACBD)\uC5D0\uC11C\uB9CC \uD544\uC694:",
118
- " 1. GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
119
- " 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",
120
- " 3. \uAD8C\uD55C \uC804\uD30C\uC5D0 ~5\uBD84 \uAC78\uB9B0\uB2E4. \uADF8 \uC804\uC5D4 403 \uC774 \uB728\uB294 \uAC8C \uC815\uC0C1\uC774\uB2E4.",
121
- "Claude \uC5D0\uAC8C `setup_playstore_connection` \uC744 \uC2DC\uD0A4\uBA74 1\uBC88\uC744 \uC790\uB3D9\uD654\uD574\uC900\uB2E4."
122
- ],
168
+ obtain: {
169
+ ko: [
170
+ "\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.",
171
+ "CI/\uD5E4\uB4DC\uB9AC\uC2A4(\uBE0C\uB77C\uC6B0\uC800 \uC5C6\uB294 \uD658\uACBD)\uC5D0\uC11C\uB9CC \uD544\uC694:",
172
+ " 1. GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
173
+ " 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",
174
+ " 3. \uAD8C\uD55C \uC804\uD30C\uC5D0 ~5\uBD84 \uAC78\uB9B0\uB2E4. \uADF8 \uC804\uC5D4 403 \uC774 \uB728\uB294 \uAC8C \uC815\uC0C1\uC774\uB2E4.",
175
+ "Claude \uC5D0\uAC8C `setup_playstore_connection` \uC744 \uC2DC\uD0A4\uBA74 1\uBC88\uC744 \uC790\uB3D9\uD654\uD574\uC900\uB2E4."
176
+ ],
177
+ en: [
178
+ "Working locally? You **don't need this** \u2014 your Google OAuth login covers most Play work.",
179
+ "Only needed in CI / headless environments (no browser):",
180
+ " 1. GCP Console \u2192 IAM & Admin \u2192 Service Accounts \u2192 Keys \u2192 Add key \u2192 Create new key \u2192 JSON",
181
+ " 2. Play Console \u2192 Settings \u2192 Users and permissions \u2192 invite that service account's email",
182
+ " 3. The grant takes ~5 min to propagate. Until then a 403 is expected, not a bug.",
183
+ "Ask Claude to run `setup_playstore_connection` and step 1 is done for you."
184
+ ]
185
+ },
123
186
  docsAnchor: "play-service-account",
124
- note: "\uC120\uD0DD \u2014 \uB85C\uCEEC\uC740 OAuth \uB85C \uAC00\uB2A5, CI/\uD5E4\uB4DC\uB9AC\uC2A4\uB294 \uD544\uC218",
187
+ note: {
188
+ ko: "\uC120\uD0DD \u2014 \uB85C\uCEEC\uC740 OAuth \uB85C \uAC00\uB2A5, CI/\uD5E4\uB4DC\uB9AC\uC2A4\uB294 \uD544\uC218",
189
+ en: "optional \u2014 OAuth covers local; required for CI / headless"
190
+ },
125
191
  detect: (home) => ({ present: hasPlaySa(home) })
126
192
  },
127
193
  {
128
194
  id: "bigquery",
129
- label: "BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815",
195
+ label: {
196
+ ko: "BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815",
197
+ en: "BigQuery service account"
198
+ },
130
199
  requirement: "optional",
131
200
  group: "core",
132
201
  file: "~/.mimi-seed/bigquery-service-account.json",
133
202
  fallbackOn: ["oauth"],
134
203
  fix: "mimi-seed auth bigquery",
135
204
  setup: { kind: "mcp-bin", bin: "mimi-seed-bigquery-auth" },
136
- obtain: [
137
- "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.",
138
- "GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
139
- "\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):",
140
- " \u2022 roles/bigquery.jobUser (\uCFFC\uB9AC \uC2E4\uD589)",
141
- " \u2022 roles/bigquery.dataViewer (\uB370\uC774\uD130\uC14B \uC77D\uAE30)"
142
- ],
205
+ obtain: {
206
+ ko: [
207
+ "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.",
208
+ "GCP Console \u2192 IAM & Admin \u2192 \uC11C\uBE44\uC2A4 \uACC4\uC815 \u2192 \uD0A4 \u2192 \uC0C8 \uD0A4 \uB9CC\uB4E4\uAE30 \u2192 JSON",
209
+ "\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):",
210
+ " \u2022 roles/bigquery.jobUser (\uCFFC\uB9AC \uC2E4\uD589)",
211
+ " \u2022 roles/bigquery.dataViewer (\uB370\uC774\uD130\uC14B \uC77D\uAE30)"
212
+ ],
213
+ en: [
214
+ "OAuth works too \u2014 a service account only matters if Workspace reauth policy (invalid_rapt) keeps blocking you.",
215
+ "GCP Console \u2192 IAM & Admin \u2192 Service Accounts \u2192 Keys \u2192 Add key \u2192 Create new key \u2192 JSON",
216
+ "You must grant that service account these IAM roles **yourself** (nothing does it for you):",
217
+ " \u2022 roles/bigquery.jobUser (run queries)",
218
+ " \u2022 roles/bigquery.dataViewer (read datasets)"
219
+ ]
220
+ },
143
221
  docsAnchor: "bigquery",
144
- note: "\uC120\uD0DD \u2014 OAuth \uD3F4\uBC31 \uAC00\uB2A5",
222
+ note: {
223
+ ko: "\uC120\uD0DD \u2014 OAuth \uD3F4\uBC31 \uAC00\uB2A5",
224
+ en: "optional \u2014 falls back to OAuth"
225
+ },
145
226
  detect: (home) => ({ present: anyFileStarting(home, "bigquery") })
146
227
  },
147
228
  {
148
229
  id: "github",
149
- label: "GitHub Actions",
230
+ label: {
231
+ ko: "GitHub Actions",
232
+ en: "GitHub Actions"
233
+ },
150
234
  requirement: "optional",
151
235
  group: "ci",
152
236
  file: "~/.mimi-seed/ci.json",
153
237
  fix: "mimi-seed auth ci",
154
238
  setup: { kind: "cli", handler: "github" },
155
- obtain: [
156
- "GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
157
- "\u26A0\uFE0F \uC2A4\uCF54\uD504 **`repo` \uC640 `workflow` \uB97C \uB458 \uB2E4** \uCCB4\uD06C\uD574\uC57C \uD55C\uB2E4.",
158
- " `workflow` \uAC00 \uC5C6\uC73C\uBA74 \uC6CC\uD06C\uD50C\uB85C dispatch \uAC00 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.",
159
- "\uADF8 \uC678: owner(\uC870\uC9C1/\uC0AC\uC6A9\uC790) \xB7 repo \uC774\uB984. GitHub Enterprise \uBA74 host URL \uB3C4."
160
- ],
239
+ obtain: {
240
+ ko: [
241
+ "GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
242
+ "\u26A0\uFE0F \uC2A4\uCF54\uD504 **`repo` \uC640 `workflow` \uB97C \uB458 \uB2E4** \uCCB4\uD06C\uD574\uC57C \uD55C\uB2E4.",
243
+ " `workflow` \uAC00 \uC5C6\uC73C\uBA74 \uC6CC\uD06C\uD50C\uB85C dispatch \uAC00 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.",
244
+ "\uADF8 \uC678: owner(\uC870\uC9C1/\uC0AC\uC6A9\uC790) \xB7 repo \uC774\uB984. GitHub Enterprise \uBA74 host URL \uB3C4."
245
+ ],
246
+ en: [
247
+ "GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens",
248
+ "\u26A0\uFE0F Check **both the `repo` and `workflow`** scopes.",
249
+ " Without `workflow`, dispatching a workflow fails with 403 (reading still works).",
250
+ "Also: owner (org/user) \xB7 repo name. Plus the host URL on GitHub Enterprise."
251
+ ]
252
+ },
161
253
  docsAnchor: "ci-github-gitlab",
162
254
  detect: (home) => ciDetect(home, "github")
163
255
  },
164
256
  {
165
257
  id: "gitlab",
166
- label: "GitLab CI",
258
+ label: {
259
+ ko: "GitLab CI",
260
+ en: "GitLab CI"
261
+ },
167
262
  requirement: "optional",
168
263
  group: "ci",
169
264
  file: "~/.mimi-seed/ci.json",
170
265
  fix: "mimi-seed auth ci",
171
266
  setup: { kind: "cli", handler: "gitlab" },
172
- obtain: [
173
- "GitLab \u2192 \uC0AC\uC6A9\uC790 \uC124\uC815 \u2192 Access Tokens \u2192 `api` \uC2A4\uCF54\uD504\uB85C \uBC1C\uAE09 (glpat-\u2026)",
174
- "\uADF8 \uC678: namespace/group \xB7 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984. self-hosted \uBA74 URL \uB3C4."
175
- ],
267
+ obtain: {
268
+ ko: [
269
+ "GitLab \u2192 \uC0AC\uC6A9\uC790 \uC124\uC815 \u2192 Access Tokens \u2192 `api` \uC2A4\uCF54\uD504\uB85C \uBC1C\uAE09 (glpat-\u2026)",
270
+ "\uADF8 \uC678: namespace/group \xB7 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984. self-hosted \uBA74 URL \uB3C4."
271
+ ],
272
+ en: [
273
+ "GitLab \u2192 User settings \u2192 Access tokens \u2192 issue one with the `api` scope (glpat-\u2026)",
274
+ "Also: namespace/group \xB7 project name. Plus the URL if self-hosted."
275
+ ]
276
+ },
176
277
  docsAnchor: "ci-github-gitlab",
177
278
  detect: (home) => ciDetect(home, "gitlab")
178
279
  },
179
280
  {
180
281
  id: "jenkins",
181
- label: "Jenkins",
282
+ label: {
283
+ ko: "Jenkins",
284
+ en: "Jenkins"
285
+ },
182
286
  requirement: "optional",
183
287
  group: "ci",
184
288
  file: "~/.mimi-seed/jenkins.json",
185
289
  fix: "mimi-seed auth jenkins",
186
290
  setup: { kind: "mcp-bin", bin: "mimi-seed-jenkins-auth" },
187
- obtain: [
188
- 'Jenkins \u2192 [\uC0AC\uC6A9\uC790 \uC774\uB984] \u2192 \uC124\uC815 \u2192 API Token \u2192 "Add new Token"',
189
- "\uBE44\uBC00\uBC88\uD638\uAC00 \uC544\uB2C8\uB77C **API Token** \uC774\uB2E4.",
190
- "\uB85C\uCEEC Jenkins \uAC00 \uC5C6\uC5B4\uB3C4 \uD68C\uC0AC/\uC6D0\uACA9 \uC11C\uBC84 URL \uC744 \uADF8\uB300\uB85C \uC4F0\uBA74 \uB41C\uB2E4."
191
- ],
291
+ obtain: {
292
+ ko: [
293
+ 'Jenkins \u2192 [\uC0AC\uC6A9\uC790 \uC774\uB984] \u2192 \uC124\uC815 \u2192 API Token \u2192 "Add new Token"',
294
+ "\uBE44\uBC00\uBC88\uD638\uAC00 \uC544\uB2C8\uB77C **API Token** \uC774\uB2E4.",
295
+ "\uB85C\uCEEC Jenkins \uAC00 \uC5C6\uC5B4\uB3C4 \uD68C\uC0AC/\uC6D0\uACA9 \uC11C\uBC84 URL \uC744 \uADF8\uB300\uB85C \uC4F0\uBA74 \uB41C\uB2E4."
296
+ ],
297
+ en: [
298
+ 'Jenkins \u2192 [your name] \u2192 Configure \u2192 API Token \u2192 "Add new Token"',
299
+ "That is an **API token**, not your password.",
300
+ "No local Jenkins needed \u2014 a company or remote server URL works just as well."
301
+ ]
302
+ },
192
303
  docsAnchor: "jenkins",
193
304
  detect: (home) => {
194
305
  const cfg = readJson(home, "jenkins.json");
@@ -197,19 +308,31 @@ var CREDENTIALS = [
197
308
  },
198
309
  {
199
310
  id: "googleads",
200
- label: "Google Ads",
311
+ label: {
312
+ ko: "Google Ads",
313
+ en: "Google Ads"
314
+ },
201
315
  requirement: "optional",
202
316
  group: "marketing",
203
317
  file: "~/.mimi-seed/google-ads.json",
204
318
  fix: "mimi-seed auth googleads",
205
319
  setup: { kind: "mcp-bin", bin: "mimi-seed-googleads-auth" },
206
- obtain: [
207
- "Google Ads \u2192 \uB3C4\uAD6C \uBC0F \uC124\uC815 \u2192 \uC124\uC815 \u2192 API \uC13C\uD130 \u2192 Developer Token \uBC1C\uAE09",
208
- "\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,",
209
- " \uC2EC\uC0AC\uC5D0\uB294 \uC2DC\uAC04\uC774 \uAC78\uB9B0\uB2E4 (\uC989\uC2DC \uBC1C\uAE09\uB418\uC9C0 \uC54A\uB294\uB2E4).",
210
- "\uADF8 \uC678: Customer ID (\uC608: 123-456-7890). MCC \uB97C \uC4F0\uBA74 \uAD00\uB9AC\uC790 \uACC4\uC815 ID \uB3C4.",
211
- "\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."
212
- ],
320
+ obtain: {
321
+ ko: [
322
+ "Google Ads \u2192 \uB3C4\uAD6C \uBC0F \uC124\uC815 \u2192 \uC124\uC815 \u2192 API \uC13C\uD130 \u2192 Developer Token \uBC1C\uAE09",
323
+ "\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,",
324
+ " \uC2EC\uC0AC\uC5D0\uB294 \uC2DC\uAC04\uC774 \uAC78\uB9B0\uB2E4 (\uC989\uC2DC \uBC1C\uAE09\uB418\uC9C0 \uC54A\uB294\uB2E4).",
325
+ "\uADF8 \uC678: Customer ID (\uC608: 123-456-7890). MCC \uB97C \uC4F0\uBA74 \uAD00\uB9AC\uC790 \uACC4\uC815 ID \uB3C4.",
326
+ "\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."
327
+ ],
328
+ en: [
329
+ "Google Ads \u2192 Tools and settings \u2192 Setup \u2192 API Center \u2192 apply for a developer token",
330
+ "\u26A0\uFE0F The token you get immediately is 'test' tier. Reaching real account data requires Google to **approve**",
331
+ " your application, and that takes time (it is not instant).",
332
+ "Also: your Customer ID (e.g. 123-456-7890), plus the manager (MCC) account ID if you use one.",
333
+ "Auth itself rides on Google OAuth's `adwords` scope \u2014 an older token may need a re-login."
334
+ ]
335
+ },
213
336
  docsAnchor: "google-ads",
214
337
  detect: (home) => {
215
338
  const cfg = readJson(home, "google-ads.json");
@@ -218,18 +341,29 @@ var CREDENTIALS = [
218
341
  },
219
342
  {
220
343
  id: "facebook",
221
- label: "Facebook \uD398\uC774\uC9C0",
344
+ label: {
345
+ ko: "Facebook \uD398\uC774\uC9C0",
346
+ en: "Facebook Page"
347
+ },
222
348
  requirement: "optional",
223
349
  group: "marketing",
224
350
  file: "~/.mimi-seed/facebook.json",
225
351
  fix: "mimi-seed auth facebook",
226
352
  setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["facebook"] },
227
- obtain: [
228
- "Meta \uAC1C\uBC1C\uC790 \uC571 + \uB300\uC0C1 \uD398\uC774\uC9C0\uC758 \uAD00\uB9AC\uC790 \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
229
- "Graph API Explorer \u2192 \uAD8C\uD55C pages_show_list, pages_manage_posts, pages_read_engagement",
230
- "\u2192 User Token \uC0DD\uC131 \u2192 /me/accounts \uD638\uCD9C \u2192 \uADF8 \uD398\uC774\uC9C0\uC758 access_token (EAA\u2026)",
231
- "long-lived \uD1A0\uD070\uC744 \uAD8C\uC7A5\uD55C\uB2E4 (\uC57D 60\uC77C)."
232
- ],
353
+ obtain: {
354
+ ko: [
355
+ "Meta \uAC1C\uBC1C\uC790 \uC571 + \uB300\uC0C1 \uD398\uC774\uC9C0\uC758 \uAD00\uB9AC\uC790 \uAD8C\uD55C\uC774 \uBA3C\uC800 \uD544\uC694\uD558\uB2E4.",
356
+ "Graph API Explorer \u2192 \uAD8C\uD55C pages_show_list, pages_manage_posts, pages_read_engagement",
357
+ "\u2192 User Token \uC0DD\uC131 \u2192 /me/accounts \uD638\uCD9C \u2192 \uADF8 \uD398\uC774\uC9C0\uC758 access_token (EAA\u2026)",
358
+ "long-lived \uD1A0\uD070\uC744 \uAD8C\uC7A5\uD55C\uB2E4 (\uC57D 60\uC77C)."
359
+ ],
360
+ en: [
361
+ "You need a Meta developer app and admin rights on the target Page first.",
362
+ "Graph API Explorer \u2192 permissions pages_show_list, pages_manage_posts, pages_read_engagement",
363
+ "\u2192 generate a User token \u2192 call /me/accounts \u2192 copy that Page's access_token (EAA\u2026)",
364
+ "Prefer a long-lived token (~60 days)."
365
+ ]
366
+ },
233
367
  docsAnchor: "facebook",
234
368
  detect: (home) => {
235
369
  const cfg = readJson(home, "facebook.json");
@@ -238,18 +372,29 @@ var CREDENTIALS = [
238
372
  },
239
373
  {
240
374
  id: "instagram",
241
- label: "Instagram",
375
+ label: {
376
+ ko: "Instagram",
377
+ en: "Instagram"
378
+ },
242
379
  requirement: "optional",
243
380
  group: "marketing",
244
381
  file: "~/.mimi-seed/instagram.json",
245
382
  fix: "mimi-seed auth instagram",
246
383
  setup: { kind: "mcp-bin", bin: "mimi-seed-social-auth", args: ["instagram"] },
247
- obtain: [
248
- "long-lived \uD1A0\uD070 \uB450 \uD615\uC2DD \uBAA8\uB450 \uC9C0\uC6D0\uD55C\uB2E4 (\uC790\uB3D9 \uAC10\uC9C0):",
249
- " \u2022 IGAA\u2026 \u2014 Instagram Login (Meta \uC2E0\uADDC \uBC29\uC2DD, Facebook \uD398\uC774\uC9C0 \uBD88\uD544\uC694)",
250
- " \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)",
251
- "\uD1A0\uD070 \uC218\uBA85\uC740 \uC57D 60\uC77C\uC774\uB2E4."
252
- ],
384
+ obtain: {
385
+ ko: [
386
+ "long-lived \uD1A0\uD070 \uB450 \uD615\uC2DD \uBAA8\uB450 \uC9C0\uC6D0\uD55C\uB2E4 (\uC790\uB3D9 \uAC10\uC9C0):",
387
+ " \u2022 IGAA\u2026 \u2014 Instagram Login (Meta \uC2E0\uADDC \uBC29\uC2DD, Facebook \uD398\uC774\uC9C0 \uBD88\uD544\uC694)",
388
+ " \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)",
389
+ "\uD1A0\uD070 \uC218\uBA85\uC740 \uC57D 60\uC77C\uC774\uB2E4."
390
+ ],
391
+ en: [
392
+ "Both long-lived token shapes are supported (auto-detected):",
393
+ " \u2022 IGAA\u2026 \u2014 Instagram Login (Meta's newer path, no Facebook Page required)",
394
+ " \u2022 EAA\u2026 \u2014 Facebook Login (needs an Instagram **Business** account linked to a FB Page)",
395
+ "Tokens last about 60 days."
396
+ ]
397
+ },
253
398
  docsAnchor: "instagram",
254
399
  detect: (home) => {
255
400
  const cfg = readJson(home, "instagram.json");
@@ -258,18 +403,31 @@ var CREDENTIALS = [
258
403
  },
259
404
  {
260
405
  id: "anthropic",
261
- label: "ANTHROPIC_API_KEY",
406
+ label: {
407
+ ko: "ANTHROPIC_API_KEY",
408
+ en: "ANTHROPIC_API_KEY"
409
+ },
262
410
  requirement: "optional",
263
411
  group: "marketing",
264
412
  fix: "export ANTHROPIC_API_KEY=sk-ant-\u2026",
265
413
  setup: { kind: "env", envVar: "ANTHROPIC_API_KEY" },
266
- obtain: [
267
- "Anthropic Console (console.anthropic.com) \u2192 API Keys \uC5D0\uC11C \uBC1C\uAE09.",
268
- "\uD658\uACBD\uBCC0\uC218\uB85C\uB9CC \uC77D\uB294\uB2E4 \u2014 \uC124\uC815 \uBA85\uB839\uC774 \uB530\uB85C \uC5C6\uB2E4.",
269
- "\uC5C6\uC5B4\uB3C4 \uB41C\uB2E4: `mimi-seed notes` \uB294 AI \uC5C6\uC774 \uCEE4\uBC0B \uBAA9\uB85D \uAE30\uBC18\uC73C\uB85C \uB3D9\uC791\uD55C\uB2E4."
270
- ],
414
+ obtain: {
415
+ ko: [
416
+ "Anthropic Console (console.anthropic.com) \u2192 API Keys \uC5D0\uC11C \uBC1C\uAE09.",
417
+ "\uD658\uACBD\uBCC0\uC218\uB85C\uB9CC \uC77D\uB294\uB2E4 \u2014 \uC124\uC815 \uBA85\uB839\uC774 \uB530\uB85C \uC5C6\uB2E4.",
418
+ "\uC5C6\uC5B4\uB3C4 \uB41C\uB2E4: `mimi-seed notes` \uB294 AI \uC5C6\uC774 \uCEE4\uBC0B \uBAA9\uB85D \uAE30\uBC18\uC73C\uB85C \uB3D9\uC791\uD55C\uB2E4."
419
+ ],
420
+ en: [
421
+ "Anthropic Console (console.anthropic.com) \u2192 API Keys.",
422
+ "Read from the environment only \u2014 there is no setup command.",
423
+ "Optional: without it, `mimi-seed notes` still works, formatting your commits instead of writing prose."
424
+ ]
425
+ },
271
426
  docsAnchor: "anthropic-api-key",
272
- note: "\uC120\uD0DD \u2014 AI \uB9B4\uB9AC\uC2A4 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\uC6A9",
427
+ note: {
428
+ ko: "\uC120\uD0DD \u2014 AI \uB9B4\uB9AC\uC2A4 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\uC6A9",
429
+ en: "optional \u2014 for AI-drafted release notes / review replies"
430
+ },
273
431
  detect: () => ({ present: Boolean(process.env.ANTHROPIC_API_KEY) })
274
432
  }
275
433
  ];
@@ -323,11 +481,13 @@ async function runMcpBin(bin, extraArgs = []) {
323
481
  const cmd = local ? bin : "npx";
324
482
  const args = local ? extraArgs : ["-y", MCP_PKG, bin, ...extraArgs];
325
483
  return new Promise((resolve) => {
326
- const child = spawn(cmd, args, { stdio: "inherit", shell: true });
484
+ const child = spawn(cmd, args, {
485
+ stdio: "inherit",
486
+ shell: true,
487
+ env: { ...process.env, MIMI_SEED_LANG: resolveLang() }
488
+ });
327
489
  child.on("error", (e) => {
328
- process.stderr.write(`
329
- \u274C ${cmd} \uC2E4\uD589 \uC2E4\uD328: ${e.message}
330
- `);
490
+ process.stderr.write(t().auth.npxFailed(cmd, e.message));
331
491
  resolve(1);
332
492
  });
333
493
  child.on("exit", (code) => resolve(code ?? 0));
@@ -583,6 +743,21 @@ import fs5 from "fs";
583
743
  import path5 from "path";
584
744
  import os4 from "os";
585
745
  var CI_CONFIG_PATH = path5.join(os4.homedir(), ".mimi-seed", "ci.json");
746
+ var M = catalog(
747
+ {
748
+ badToken: (provider, status) => `${provider} ${status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544`,
749
+ noScope: (scopes) => `\uD1A0\uD070\uC5D0 \`workflow\` \uC2A4\uCF54\uD504\uAC00 \uC5C6\uC5B4 (\uD604\uC7AC: ${scopes || "\uC5C6\uC74C"}). \uC6CC\uD06C\uD50C\uB85C \uC2E4\uD589\uC774 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.`,
750
+ pollFailed: (provider) => `${provider} API \uC5F0\uC18D \uC624\uB958 3\uD68C`,
751
+ pollFailedHttp: (provider, status) => `${provider} API \uC5F0\uC18D \uC624\uB958 (HTTP ${status})`
752
+ },
753
+ {
754
+ badToken: (provider, status) => `${provider} ${status} \u2014 the token is not valid`,
755
+ noScope: (scopes) => `The token is missing the \`workflow\` scope (currently: ${scopes || "none"}). Triggering a workflow will be blocked with a 403.`,
756
+ // 프로바이더 이름("GitHub API")은 두 언어 모두에 남는다 — 테스트가 그걸로 단언한다.
757
+ pollFailed: (provider) => `${provider} API failed 3 times in a row`,
758
+ pollFailedHttp: (provider, status) => `${provider} API failed 3 times in a row (HTTP ${status})`
759
+ }
760
+ );
586
761
  function loadCiProviderConfig() {
587
762
  try {
588
763
  const cfg = JSON.parse(fs5.readFileSync(CI_CONFIG_PATH, "utf-8"));
@@ -618,21 +793,18 @@ async function verifyCiToken(cfg) {
618
793
  headers: { Authorization: `Bearer ${probe.token}`, Accept: "application/vnd.github+json" }
619
794
  });
620
795
  if (!res2.ok) {
621
- return { ok: false, reason: `GitHub ${res2.status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544` };
796
+ return { ok: false, reason: M().badToken("GitHub", res2.status) };
622
797
  }
623
798
  const user2 = await res2.json();
624
799
  const scopes = res2.headers.get("x-oauth-scopes");
625
800
  if (scopes !== null && !scopes.split(/,\s*/).filter(Boolean).includes("workflow")) {
626
- return {
627
- ok: false,
628
- reason: `\uD1A0\uD070\uC5D0 \`workflow\` \uC2A4\uCF54\uD504\uAC00 \uC5C6\uC5B4 (\uD604\uC7AC: ${scopes || "\uC5C6\uC74C"}). \uC6CC\uD06C\uD50C\uB85C \uC2E4\uD589\uC774 403 \uC73C\uB85C \uB9C9\uD78C\uB2E4.`
629
- };
801
+ return { ok: false, reason: M().noScope(scopes) };
630
802
  }
631
803
  return { ok: true, login: user2.login };
632
804
  }
633
805
  const res = await fetch(`${glBase(probe)}/user`, { headers: { "PRIVATE-TOKEN": probe.token } });
634
806
  if (!res.ok) {
635
- return { ok: false, reason: `GitLab ${res.status} \u2014 \uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544` };
807
+ return { ok: false, reason: M().badToken("GitLab", res.status) };
636
808
  }
637
809
  const user = await res.json();
638
810
  return { ok: true, login: user.username };
@@ -690,12 +862,12 @@ async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, interval
690
862
  );
691
863
  } catch {
692
864
  consecutiveErrors++;
693
- if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958 3\uD68C");
865
+ if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitHub"));
694
866
  continue;
695
867
  }
696
868
  if (!res.ok) {
697
869
  consecutiveErrors++;
698
- if (consecutiveErrors >= 3) throw new Error(`GitHub API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
870
+ if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitHub", res.status));
699
871
  continue;
700
872
  }
701
873
  consecutiveErrors = 0;
@@ -741,12 +913,12 @@ async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3
741
913
  );
742
914
  } catch {
743
915
  consecutiveErrors++;
744
- if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958 3\uD68C");
916
+ if (consecutiveErrors >= 3) throw new Error(M().pollFailed("GitLab"));
745
917
  continue;
746
918
  }
747
919
  if (!res.ok) {
748
920
  consecutiveErrors++;
749
- if (consecutiveErrors >= 3) throw new Error(`GitLab API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
921
+ if (consecutiveErrors >= 3) throw new Error(M().pollFailedHttp("GitLab", res.status));
750
922
  continue;
751
923
  }
752
924
  consecutiveErrors = 0;
@@ -772,6 +944,144 @@ var PHASE_ICON = {
772
944
  function log(msg) {
773
945
  process.stdout.write(msg + "\n");
774
946
  }
947
+ var M2 = catalog(
948
+ {
949
+ // Jenkins / 빌드
950
+ jenkinsTriggerFailed: (status, body) => `Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${status}: ${body}`,
951
+ buildStatusFailed: (status) => `\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${status}`,
952
+ jenkinsConnErrorFatal: "Jenkins \uC5F0\uACB0 \uC624\uB958 3\uD68C \uC5F0\uC18D \u2014 \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uC138\uC694",
953
+ jenkinsConnErrorRetry: (n) => `Jenkins \uC5F0\uACB0 \uC624\uB958 (${n}/3\uD68C), \uC7AC\uC2DC\uB3C4...`,
954
+ buildRunning: "\uBE4C\uB4DC \uC9C4\uD589 \uC911",
955
+ buildTimeout: "\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)",
956
+ // 서버 스트림
957
+ serverDeployFailed: (status, body) => `\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${status}: ${body}`,
958
+ noSseStream: "SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C",
959
+ // CI provider 설정 프롬프트
960
+ githubSetupTitle: "GitHub Actions \uC124\uC815",
961
+ gitlabSetupTitle: "GitLab CI \uC124\uC815",
962
+ githubTokenPrompt: " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): ",
963
+ gitlabTokenPrompt: " GitLab Personal Access Token: ",
964
+ githubOwnerPrompt: " Owner (org/user): ",
965
+ gitlabOwnerPrompt: " Namespace/group: ",
966
+ repoPrompt: " Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ",
967
+ githubHostPrompt: " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): ",
968
+ gitlabHostPrompt: " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ",
969
+ githubSaved: "\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json",
970
+ gitlabSaved: "\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json",
971
+ noCiConfig: "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",
972
+ // GitHub / GitLab 빌드
973
+ workflowRequired: "--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)",
974
+ ghTrigger: (workflow, ref) => `\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${workflow} @ ${ref}`,
975
+ ghRunIdFailed: "GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.",
976
+ ghRunId: (id, url) => ` Run ID: ${id} \u2192 ${url}`,
977
+ glTrigger: (ref) => `\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${ref}`,
978
+ glPipelineId: (id, url) => ` Pipeline ID: ${id} \u2192 ${url}`,
979
+ waitingForCompletion: " \uC644\uB8CC \uB300\uAE30 \uC911...",
980
+ buildSucceeded: (n) => `\u2705 \uBE4C\uB4DC #${n} \uC131\uACF5`,
981
+ buildEnded: (result) => `\uBE4C\uB4DC \uC885\uB8CC: ${result}`,
982
+ alreadyBuiltHint: (versionCode, platform) => ` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${versionCode} --platform ${platform}`,
983
+ // cmdDeploy
984
+ noAccount: "\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589.",
985
+ title: (platform) => `mimi-seed deploy \u2014 ${platform}`,
986
+ dryRunNotice: " [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
987
+ ciLine: (kind) => ` CI: ${kind}`,
988
+ noJenkinsConfig: "Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9.",
989
+ noJenkinsJob: (platform) => `${platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`,
990
+ jenkinsTrigger: (job) => `\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${job}`,
991
+ noQueueItem: " \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.",
992
+ queueItem: (id) => ` Queue item: ${id}`,
993
+ waitingForBuildNumber: " \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...",
994
+ noBuildNumber: " \uBE4C\uB4DC \uBC88\uD638\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --skip-build + --version-code \uB85C \uC7AC\uC2DC\uB3C4 \uAC00\uB2A5.",
995
+ buildStarted: (n) => ` \uBE4C\uB4DC #${n} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`,
996
+ buildFailed: (result) => `\uBE4C\uB4DC \uC2E4\uD328: ${result}`,
997
+ jenkinsLink: (url) => ` Jenkins: ${url}`,
998
+ versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
999
+ noProviderConfig: (kind) => `${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`,
1000
+ versionCodeUnsuitable: (kind, buildId) => `\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`,
1001
+ recommendation: " \uAD8C\uC7A5 \uC0AC\uD56D:",
1002
+ recommendationCi: " \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825",
1003
+ recommendationNext: " \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>",
1004
+ versionCodeUnknown: "versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694.",
1005
+ appLine: (name, id) => ` \uC571: ${name} (${id})`,
1006
+ noAppId: "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.",
1007
+ // 프로덕션 확인
1008
+ targetIos: "App Store \uC2EC\uC0AC \uC81C\uCD9C",
1009
+ targetAndroid: "Play Store production \uD2B8\uB799",
1010
+ realDeploy: (platform, versionCode, target) => `\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${platform} \xB7 versionCode ${versionCode} \u2192 ${target}`,
1011
+ confirmPrompt: "\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: ",
1012
+ confirmCancelled: "\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)",
1013
+ pipelineStarting: "\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...",
1014
+ done: "\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."
1015
+ },
1016
+ {
1017
+ // Jenkins / build
1018
+ jenkinsTriggerFailed: (status, body) => `Jenkins trigger failed ${status}: ${body}`,
1019
+ buildStatusFailed: (status) => `Failed to fetch build status ${status}`,
1020
+ jenkinsConnErrorFatal: "Jenkins connection failed 3 times in a row \u2014 check your network",
1021
+ jenkinsConnErrorRetry: (n) => `Jenkins connection error (${n}/3), retrying...`,
1022
+ buildRunning: "Build running",
1023
+ buildTimeout: "Build timed out (30 min)",
1024
+ // Server stream
1025
+ serverDeployFailed: (status, body) => `Server deploy failed ${status}: ${body}`,
1026
+ noSseStream: "No SSE stream",
1027
+ // CI provider setup prompts
1028
+ githubSetupTitle: "GitHub Actions setup",
1029
+ gitlabSetupTitle: "GitLab CI setup",
1030
+ githubTokenPrompt: " GitHub Personal Access Token (repo+workflow scopes): ",
1031
+ gitlabTokenPrompt: " GitLab Personal Access Token: ",
1032
+ githubOwnerPrompt: " Owner (org/user): ",
1033
+ gitlabOwnerPrompt: " Namespace/group: ",
1034
+ repoPrompt: " Repo name (without the path): ",
1035
+ githubHostPrompt: " GitHub Enterprise host (optional, Enter = github.com): ",
1036
+ gitlabHostPrompt: " GitLab self-hosted URL (optional, Enter = gitlab.com): ",
1037
+ githubSaved: "\u2705 GitHub Actions config saved \u2192 ~/.mimi-seed/ci.json",
1038
+ gitlabSaved: "\u2705 GitLab CI config saved \u2192 ~/.mimi-seed/ci.json",
1039
+ noCiConfig: "No CI configured. Run one of:\n \u2022 mimi-seed deploy setup-jenkins\n \u2022 mimi-seed deploy setup-github\n \u2022 mimi-seed deploy setup-gitlab",
1040
+ // GitHub / GitLab build
1041
+ workflowRequired: "--workflow is required (e.g. --workflow deploy.yml)",
1042
+ ghTrigger: (workflow, ref) => `\u{1F528} Triggering GitHub Actions: ${workflow} @ ${ref}`,
1043
+ ghRunIdFailed: "Could not resolve the GitHub Actions run_id. Check with ci_list_recent_builds in a moment.",
1044
+ ghRunId: (id, url) => ` Run ID: ${id} \u2192 ${url}`,
1045
+ glTrigger: (ref) => `\u{1F528} Triggering GitLab Pipeline: ${ref}`,
1046
+ glPipelineId: (id, url) => ` Pipeline ID: ${id} \u2192 ${url}`,
1047
+ waitingForCompletion: " Waiting for it to finish...",
1048
+ buildSucceeded: (n) => `\u2705 Build #${n} succeeded`,
1049
+ buildEnded: (result) => `Build ended: ${result}`,
1050
+ alreadyBuiltHint: (versionCode, platform) => ` If the build already finished: mimi-seed deploy --skip-build --version-code ${versionCode} --platform ${platform}`,
1051
+ // cmdDeploy
1052
+ noAccount: "No account connected. Run `mimi-seed init`.",
1053
+ title: (platform) => `mimi-seed deploy \u2014 ${platform}`,
1054
+ dryRunNotice: " [dry-run] nothing will actually be deployed",
1055
+ ciLine: (kind) => ` CI: ${kind}`,
1056
+ noJenkinsConfig: "Jenkins is not configured. Run `mimi-seed deploy setup-jenkins`, or use --skip-build.",
1057
+ noJenkinsJob: (platform) => `No Jenkins job configured for ${platform}. Run setup-jenkins.`,
1058
+ jenkinsTrigger: (job) => `\u{1F528} Triggering Jenkins build: ${job}`,
1059
+ noQueueItem: " \u26A0 Could not read the queue item ID. The build may have started anyway.",
1060
+ queueItem: (id) => ` Queue item: ${id}`,
1061
+ waitingForBuildNumber: " Waiting for the build number...",
1062
+ noBuildNumber: " Could not read the build number. Retry with --skip-build + --version-code.",
1063
+ buildStarted: (n) => ` Build #${n} started. Waiting for it to finish...`,
1064
+ buildFailed: (result) => `Build failed: ${result}`,
1065
+ jenkinsLink: (url) => ` Jenkins: ${url}`,
1066
+ versionCodeFromBuild: (n) => ` versionCode = buildNumber (${n})`,
1067
+ noProviderConfig: (kind) => `${kind} is not configured. Run setup-${kind}.`,
1068
+ versionCodeUnsuitable: (kind, buildId) => `\u2717 No versionCode given (${kind} run_id ${buildId} is not usable as a versionCode)`,
1069
+ recommendation: " Recommended:",
1070
+ recommendationCi: " \u2022 Decide the versionCode inside the CI workflow and print it",
1071
+ recommendationNext: " \u2022 Then run: mimi-seed deploy --skip-build --version-code <N>",
1072
+ versionCodeUnknown: "Unknown versionCode. Pass it with --version-code <N>.",
1073
+ appLine: (name, id) => ` App: ${name} (${id})`,
1074
+ noAppId: "Could not resolve the appId. Pass --app <id>, or register the app with `mimi-seed init`.",
1075
+ // Production confirmation
1076
+ targetIos: "App Store review submission",
1077
+ targetAndroid: "Play Store production track",
1078
+ realDeploy: (platform, versionCode, target) => `\u26A0 Real deploy: ${platform} \xB7 versionCode ${versionCode} \u2192 ${target}`,
1079
+ confirmPrompt: "Continue? [y/N]: ",
1080
+ confirmCancelled: "Cancelled. (use --yes to skip this confirmation)",
1081
+ pipelineStarting: "\u{1F4E1} Starting the server deploy pipeline...",
1082
+ done: "Done. Check the deploy status in Play Console."
1083
+ }
1084
+ );
775
1085
  function jenkinsHeaders(cfg) {
776
1086
  const creds = Buffer.from(`${cfg.username || "admin"}:${cfg.token}`).toString("base64");
777
1087
  return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
@@ -781,7 +1091,7 @@ async function triggerBuild(cfg, jobName, params) {
781
1091
  const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
782
1092
  const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
783
1093
  if (!res.ok) {
784
- throw new Error(`Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${res.status}: ${await res.text()}`);
1094
+ throw new Error(M2().jenkinsTriggerFailed(res.status, await res.text()));
785
1095
  }
786
1096
  const location = res.headers.get("Location") ?? "";
787
1097
  const match = location.match(/\/queue\/item\/(\d+)\//);
@@ -797,7 +1107,7 @@ async function getQueueBuildNumber(cfg, queueItemId) {
797
1107
  async function getBuildStatus(cfg, jobName, buildNumber) {
798
1108
  const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
799
1109
  const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
800
- if (!res.ok) throw new Error(`\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${res.status}`);
1110
+ if (!res.ok) throw new Error(M2().buildStatusFailed(res.status));
801
1111
  const data = await res.json();
802
1112
  return {
803
1113
  building: data.building ?? true,
@@ -818,18 +1128,18 @@ async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60
818
1128
  consecutiveErrors = 0;
819
1129
  } catch {
820
1130
  consecutiveErrors++;
821
- 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");
822
- process.stdout.write(`\r \u26A0 Jenkins \uC5F0\uACB0 \uC624\uB958 (${consecutiveErrors}/3\uD68C), \uC7AC\uC2DC\uB3C4... `);
1131
+ if (consecutiveErrors >= 3) throw new Error(M2().jenkinsConnErrorFatal);
1132
+ process.stdout.write(`\r \u26A0 ${M2().jenkinsConnErrorRetry(consecutiveErrors)} `);
823
1133
  continue;
824
1134
  }
825
1135
  dots = (dots + 1) % 4;
826
- process.stdout.write(`\r \u23F3 \uBE4C\uB4DC \uC9C4\uD589 \uC911${".".repeat(dots + 1)} `);
1136
+ process.stdout.write(`\r \u23F3 ${M2().buildRunning}${".".repeat(dots + 1)} `);
827
1137
  if (!status.building) {
828
1138
  process.stdout.write("\n");
829
1139
  return status.result ?? "FAILURE";
830
1140
  }
831
1141
  }
832
- throw new Error("\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)");
1142
+ throw new Error(M2().buildTimeout);
833
1143
  }
834
1144
  async function streamDeploy(webBase, token, body) {
835
1145
  const res = await fetch(`${webBase}/api/deploy`, {
@@ -842,10 +1152,10 @@ async function streamDeploy(webBase, token, body) {
842
1152
  });
843
1153
  if (!res.ok) {
844
1154
  const text = await res.text().catch(() => "");
845
- throw new Error(`\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${res.status}: ${text.slice(0, 200)}`);
1155
+ throw new Error(M2().serverDeployFailed(res.status, text.slice(0, 200)));
846
1156
  }
847
1157
  const reader = res.body?.getReader();
848
- if (!reader) throw new Error("SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
1158
+ if (!reader) throw new Error(M2().noSseStream);
849
1159
  const decoder = new TextDecoder();
850
1160
  let buf = "";
851
1161
  while (true) {
@@ -905,12 +1215,13 @@ async function promptGitProviderSetup(provider) {
905
1215
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
906
1216
  const ask2 = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
907
1217
  const isGh = provider === "github";
908
- log(kleur.bold(isGh ? "GitHub Actions \uC124\uC815" : "GitLab CI \uC124\uC815"));
909
- const tokenLabel = isGh ? " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): " : " GitLab Personal Access Token: ";
1218
+ const m = M2();
1219
+ log(kleur.bold(isGh ? m.githubSetupTitle : m.gitlabSetupTitle));
1220
+ const tokenLabel = isGh ? m.githubTokenPrompt : m.gitlabTokenPrompt;
910
1221
  const token = await ask2(tokenLabel);
911
- const owner = await ask2(isGh ? " Owner (org/user): " : " Namespace/group: ");
912
- const repo = await ask2(" Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ");
913
- const hostPrompt = isGh ? " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): " : " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ";
1222
+ const owner = await ask2(isGh ? m.githubOwnerPrompt : m.gitlabOwnerPrompt);
1223
+ const repo = await ask2(m.repoPrompt);
1224
+ const hostPrompt = isGh ? m.githubHostPrompt : m.gitlabHostPrompt;
914
1225
  const host = await ask2(hostPrompt);
915
1226
  rl.close();
916
1227
  return {
@@ -925,38 +1236,36 @@ function resolveCi(ciOption, jenkins, ciProvider) {
925
1236
  if (ciOption !== "auto") return ciOption;
926
1237
  if (jenkins?.url && jenkins.token) return "jenkins";
927
1238
  if (ciProvider) return ciProvider.provider;
928
- throw new Error(
929
- "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"
930
- );
1239
+ throw new Error(M2().noCiConfig);
931
1240
  }
932
1241
  async function runGitProviderBuild(cfg, args) {
933
1242
  let runUrl = "";
934
1243
  let runId;
935
1244
  if (cfg.provider === "github") {
936
1245
  if (!args.workflow) {
937
- throw new Error("--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)");
1246
+ throw new Error(M2().workflowRequired);
938
1247
  }
939
- log(`\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${kleur.cyan(args.workflow)} @ ${args.ref}`);
1248
+ log(M2().ghTrigger(kleur.cyan(args.workflow), args.ref));
940
1249
  const inputs = {};
941
1250
  if (args.appId) inputs.MIMI_APP_ID = args.appId;
942
1251
  inputs.PLATFORM = args.platform;
943
1252
  const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
944
1253
  if (!result2) {
945
- throw new Error("GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.");
1254
+ throw new Error(M2().ghRunIdFailed);
946
1255
  }
947
1256
  runId = result2.runId;
948
1257
  runUrl = result2.url;
949
- log(kleur.dim(` Run ID: ${runId} \u2192 ${runUrl}`));
1258
+ log(kleur.dim(M2().ghRunId(runId, runUrl)));
950
1259
  } else {
951
- log(`\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${args.ref}`);
1260
+ log(M2().glTrigger(args.ref));
952
1261
  const variables = { PLATFORM: args.platform };
953
1262
  if (args.appId) variables.MIMI_APP_ID = args.appId;
954
1263
  const result2 = await glTriggerPipeline(cfg, args.ref, variables);
955
1264
  runId = result2.pipelineId;
956
1265
  runUrl = result2.url;
957
- log(kleur.dim(` Pipeline ID: ${runId} \u2192 ${runUrl}`));
1266
+ log(kleur.dim(M2().glPipelineId(runId, runUrl)));
958
1267
  }
959
- log(" \uC644\uB8CC \uB300\uAE30 \uC911...");
1268
+ log(M2().waitingForCompletion);
960
1269
  let dots = 0;
961
1270
  const onTick = (status) => {
962
1271
  dots = (dots + 1) % 4;
@@ -965,19 +1274,19 @@ async function runGitProviderBuild(cfg, args) {
965
1274
  const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
966
1275
  process.stdout.write("\n");
967
1276
  if (result === "success") {
968
- log(kleur.green(`\u2705 \uBE4C\uB4DC #${runId} \uC131\uACF5`));
1277
+ log(kleur.green(M2().buildSucceeded(runId)));
969
1278
  return runId;
970
1279
  }
971
- log(kleur.red(`\uBE4C\uB4DC \uC885\uB8CC: ${result}`));
1280
+ log(kleur.red(M2().buildEnded(result)));
972
1281
  log(kleur.dim(` ${runUrl}`));
973
- log(kleur.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code <N> --platform ${args.platform}`));
1282
+ log(kleur.dim(M2().alreadyBuiltHint("<N>", args.platform)));
974
1283
  process.exit(1);
975
1284
  }
976
1285
  async function cmdDeploy(argv) {
977
1286
  const args = parseArgs(argv);
978
1287
  const cfg = await getEffectiveConfig();
979
1288
  if (!cfg) {
980
- log(kleur.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589."));
1289
+ log(kleur.red(M2().noAccount));
981
1290
  process.exit(1);
982
1291
  }
983
1292
  if (args.setupJenkins) {
@@ -988,17 +1297,17 @@ async function cmdDeploy(argv) {
988
1297
  if (args.setupGithub) {
989
1298
  const ciCfg = await promptGitProviderSetup("github");
990
1299
  saveCiProviderConfig(ciCfg);
991
- log(kleur.green(`\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
1300
+ log(kleur.green(M2().githubSaved));
992
1301
  return;
993
1302
  }
994
1303
  if (args.setupGitlab) {
995
1304
  const ciCfg = await promptGitProviderSetup("gitlab");
996
1305
  saveCiProviderConfig(ciCfg);
997
- log(kleur.green(`\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
1306
+ log(kleur.green(M2().gitlabSaved));
998
1307
  return;
999
1308
  }
1000
- log(kleur.bold(`mimi-seed deploy \u2014 ${args.platform}`));
1001
- if (args.dryRun) log(kleur.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
1309
+ log(kleur.bold(M2().title(args.platform)));
1310
+ if (args.dryRun) log(kleur.yellow(M2().dryRunNotice));
1002
1311
  log("");
1003
1312
  let versionCode = args.versionCode;
1004
1313
  if (!args.skipBuild) {
@@ -1006,30 +1315,30 @@ async function cmdDeploy(argv) {
1006
1315
  migrateLegacyJenkins();
1007
1316
  const jenkinsCfg = loadJenkinsConfig() ?? void 0;
1008
1317
  const kind = resolveCi(args.ci, jenkinsCfg, ciProvider);
1009
- log(kleur.dim(` CI: ${kind}`));
1318
+ log(kleur.dim(M2().ciLine(kind)));
1010
1319
  if (kind === "jenkins") {
1011
1320
  if (!jenkinsCfg?.url || !jenkinsCfg?.token) {
1012
- log(kleur.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
1321
+ log(kleur.yellow(M2().noJenkinsConfig));
1013
1322
  process.exit(1);
1014
1323
  }
1015
1324
  const jenkins = jenkinsCfg;
1016
1325
  const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1017
1326
  if (!jobName) {
1018
- log(kleur.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
1327
+ log(kleur.red(M2().noJenkinsJob(args.platform)));
1019
1328
  process.exit(1);
1020
1329
  }
1021
- log(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur.cyan(jobName)}`);
1330
+ log(M2().jenkinsTrigger(kleur.cyan(jobName)));
1022
1331
  const buildParams = {};
1023
1332
  if (args.appId) buildParams.MIMI_APP_ID = args.appId;
1024
1333
  const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1025
1334
  if (!queueItemId) {
1026
- log(kleur.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."));
1335
+ log(kleur.yellow(M2().noQueueItem));
1027
1336
  } else {
1028
- log(kleur.dim(` Queue item: ${queueItemId}`));
1337
+ log(kleur.dim(M2().queueItem(queueItemId)));
1029
1338
  }
1030
1339
  let buildNumber = null;
1031
1340
  if (queueItemId) {
1032
- log(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
1341
+ log(M2().waitingForBuildNumber);
1033
1342
  for (let i = 0; i < 6; i++) {
1034
1343
  await new Promise((r) => setTimeout(r, 5e3));
1035
1344
  buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
@@ -1037,77 +1346,77 @@ async function cmdDeploy(argv) {
1037
1346
  }
1038
1347
  }
1039
1348
  if (!buildNumber) {
1040
- log(kleur.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."));
1349
+ log(kleur.yellow(M2().noBuildNumber));
1041
1350
  process.exit(1);
1042
1351
  }
1043
- log(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
1352
+ log(M2().buildStarted(buildNumber));
1044
1353
  const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1045
1354
  if (result !== "SUCCESS") {
1046
- log(kleur.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
1047
- log(kleur.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
1048
- log(kleur.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
1355
+ log(kleur.red(M2().buildFailed(result)));
1356
+ log(kleur.dim(M2().jenkinsLink(`${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`)));
1357
+ log(kleur.dim(M2().alreadyBuiltHint(String(buildNumber), args.platform)));
1049
1358
  process.exit(1);
1050
1359
  }
1051
- log(kleur.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
1360
+ log(kleur.green(M2().buildSucceeded(buildNumber)));
1052
1361
  if (!versionCode) {
1053
1362
  versionCode = buildNumber;
1054
- log(kleur.dim(` versionCode = buildNumber (${versionCode})`));
1363
+ log(kleur.dim(M2().versionCodeFromBuild(versionCode)));
1055
1364
  }
1056
1365
  } else {
1057
1366
  if (!ciProvider) {
1058
- log(kleur.red(`${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`));
1367
+ log(kleur.red(M2().noProviderConfig(kind)));
1059
1368
  process.exit(1);
1060
1369
  }
1061
1370
  const buildId = await runGitProviderBuild(ciProvider, args);
1062
1371
  if (!versionCode) {
1063
- log(kleur.red(`\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`));
1064
- log(kleur.dim(" \uAD8C\uC7A5 \uC0AC\uD56D:"));
1065
- log(kleur.dim(" \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825"));
1066
- log(kleur.dim(" \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>"));
1372
+ log(kleur.red(M2().versionCodeUnsuitable(kind, buildId)));
1373
+ log(kleur.dim(M2().recommendation));
1374
+ log(kleur.dim(M2().recommendationCi));
1375
+ log(kleur.dim(M2().recommendationNext));
1067
1376
  process.exit(1);
1068
1377
  }
1069
1378
  }
1070
1379
  }
1071
1380
  if (!versionCode) {
1072
- log(kleur.red("versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694."));
1381
+ log(kleur.red(M2().versionCodeUnknown));
1073
1382
  process.exit(1);
1074
1383
  }
1075
1384
  let appId = args.appId;
1076
1385
  if (!appId) {
1077
- const { mcpCall } = await import("./mcp-client-SLIKR42C.js");
1386
+ const { mcpCall } = await import("./mcp-client-PVWDCYJN.js");
1078
1387
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
1079
1388
  if (!r.isError) {
1080
1389
  try {
1081
1390
  const apps = JSON.parse(r.text);
1082
1391
  if (apps.length > 0) {
1083
1392
  appId = apps[0].id;
1084
- log(kleur.dim(` \uC571: ${apps[0].name} (${appId})`));
1393
+ log(kleur.dim(M2().appLine(apps[0].name, appId)));
1085
1394
  }
1086
1395
  } catch {
1087
1396
  }
1088
1397
  }
1089
1398
  }
1090
1399
  if (!appId) {
1091
- log(kleur.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."));
1400
+ log(kleur.red(M2().noAppId));
1092
1401
  process.exit(1);
1093
1402
  }
1094
1403
  const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
1095
1404
  if (needsConfirm) {
1096
- const target = args.platform === "ios" ? "App Store \uC2EC\uC0AC \uC81C\uCD9C" : "Play Store production \uD2B8\uB799";
1405
+ const target = args.platform === "ios" ? M2().targetIos : M2().targetAndroid;
1097
1406
  log("");
1098
- log(kleur.yellow(`\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${args.platform} \xB7 versionCode ${versionCode} \u2192 ${target}`));
1407
+ log(kleur.yellow(M2().realDeploy(args.platform, versionCode, target)));
1099
1408
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1100
1409
  const answer = await new Promise(
1101
- (resolve) => rl.question(kleur.bold("\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: "), (a) => resolve(a.trim().toLowerCase()))
1410
+ (resolve) => rl.question(kleur.bold(M2().confirmPrompt), (a) => resolve(a.trim().toLowerCase()))
1102
1411
  );
1103
1412
  rl.close();
1104
1413
  if (answer !== "y" && answer !== "yes") {
1105
- log(kleur.dim("\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)"));
1414
+ log(kleur.dim(M2().confirmCancelled));
1106
1415
  return;
1107
1416
  }
1108
1417
  }
1109
1418
  log("");
1110
- log("\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...");
1419
+ log(M2().pipelineStarting);
1111
1420
  log("");
1112
1421
  const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
1113
1422
  await streamDeploy(cfg.webBase, cfg.token, {
@@ -1121,7 +1430,7 @@ async function cmdDeploy(argv) {
1121
1430
  dryRun: args.dryRun
1122
1431
  });
1123
1432
  log("");
1124
- log(kleur.bold("\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
1433
+ log(kleur.bold(M2().done));
1125
1434
  log(kleur.dim(" https://play.google.com/console/developers"));
1126
1435
  }
1127
1436
 
@@ -1169,15 +1478,16 @@ function statusLine(spec, detected, platforms) {
1169
1478
  else if (isSatisfied(spec, detected)) mark = kleur2.yellow("~");
1170
1479
  else if (spec.requirement === "optional" || !relevant) mark = kleur2.dim("\xB7");
1171
1480
  else mark = kleur2.red("\u2717");
1172
- const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${spec.note ?? "\uD3F4\uBC31\uC73C\uB85C \uB3D9\uC791 \uC911"}`) : kleur2.dim(`\u2192 ${spec.fix}`);
1173
- return ` ${mark} ${spec.label.padEnd(24)} ${tail}`;
1481
+ const tail = d.present ? kleur2.dim(d.detail ?? "") : isSatisfied(spec, detected) ? kleur2.dim(`${credNote(spec) ?? t().setup.fallbackWorking}`) : kleur2.dim(`\u2192 ${spec.fix}`);
1482
+ return ` ${mark} ${credLabel(spec).padEnd(24)} ${tail}`;
1174
1483
  }
1175
1484
  function printStatus(detected, platforms) {
1176
- log2(kleur2.bold("\uC5F0\uACB0 \uC0C1\uD0DC ") + kleur2.dim("(~/.mimi-seed)"));
1485
+ const m = t().setup;
1486
+ log2(kleur2.bold(m.statusTitle + " ") + kleur2.dim(m.statusDir));
1177
1487
  log2();
1178
1488
  for (const group of ["core", "ci", "marketing"]) {
1179
1489
  const specs = CREDENTIALS.filter((c) => c.group === group);
1180
- const title = group === "core" ? "\uD575\uC2EC" : group === "ci" ? "\uBE4C\uB4DC / CI" : "\uB9C8\uCF00\uD305 \xB7 AI";
1490
+ const title = group === "core" ? m.groupCore : group === "ci" ? m.groupCi : m.groupMarketing;
1181
1491
  log2(kleur2.dim(` \u2500\u2500 ${title} \u2500\u2500`));
1182
1492
  for (const spec of specs) log2(statusLine(spec, detected, platforms));
1183
1493
  log2();
@@ -1186,55 +1496,68 @@ function printStatus(detected, platforms) {
1186
1496
  function ask(q) {
1187
1497
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
1188
1498
  return new Promise((resolve) => {
1189
- rl.question(q, (a) => {
1499
+ let done = false;
1500
+ const finish = (value) => {
1501
+ if (done) return;
1502
+ done = true;
1190
1503
  rl.close();
1191
- resolve(a.trim());
1192
- });
1193
- rl.on("close", () => resolve(""));
1504
+ resolve(value);
1505
+ };
1506
+ rl.question(q, (a) => finish(a.trim()));
1507
+ rl.on("close", () => finish(""));
1194
1508
  });
1195
1509
  }
1196
1510
  function printObtain(spec) {
1197
1511
  log2();
1198
- log2(kleur2.bold(` ${spec.label} \u2014 \uBBF8\uB9AC \uC900\uBE44\uD560 \uAC83`));
1199
- for (const line of spec.obtain) log2(kleur2.dim(` ${line}`));
1512
+ log2(kleur2.bold(t().setup.obtainTitle(credLabel(spec))));
1513
+ for (const line of credObtain(spec)) log2(kleur2.dim(` ${line}`));
1200
1514
  if (spec.docsAnchor) {
1201
- log2(kleur2.dim(` \uC790\uC138\uD788: docs/credentials.md#${spec.docsAnchor}`));
1515
+ log2(kleur2.dim(t().setup.obtainMore(spec.docsAnchor)));
1202
1516
  }
1203
1517
  log2();
1204
1518
  }
1519
+ async function ensureLangChosen() {
1520
+ if (!isLangUnset()) return;
1521
+ const answer = await ask(t().lang.ask);
1522
+ const lang = answer.trim() === "2" || answer.trim().toLowerCase() === "en" ? "en" : "ko";
1523
+ writeSettings({ lang });
1524
+ process.env.MIMI_SEED_LANG = lang;
1525
+ log2(kleur2.green(t().lang.saved(lang)));
1526
+ log2();
1527
+ }
1205
1528
  async function connectOne(spec) {
1206
1529
  switch (spec.setup.kind) {
1207
1530
  case "mcp-bin": {
1208
1531
  const code = await runMcpBin(spec.setup.bin, spec.setup.args ?? []);
1209
1532
  if (code !== 0) {
1210
- log2(kleur2.yellow(` \u26A0 ${spec.label} \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC5B4 (exit ${code}). \uB098\uC911\uC5D0 \uB2E4\uC2DC: ${spec.fix}`));
1533
+ log2(kleur2.yellow(t().setup.binFailed(credLabel(spec), code, spec.fix)));
1211
1534
  }
1212
1535
  return true;
1213
1536
  }
1214
1537
  case "cli": {
1215
1538
  const provider = spec.setup.handler;
1216
1539
  const cfg = await promptGitProviderSetup(provider);
1217
- log2(kleur2.dim(" \u{1F50E} \uD1A0\uD070 \uAC80\uC99D \uC911..."));
1540
+ log2(kleur2.dim(t().setup.verifying));
1218
1541
  const check = await verifyCiToken(cfg);
1219
1542
  if (!check.ok) {
1220
- log2(kleur2.red(` \u274C \uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328: ${check.reason}`));
1221
- log2(kleur2.dim(` \uC800\uC7A5\uD558\uC9C0 \uC54A\uC558\uC5B4. \uB2E4\uC2DC: ${spec.fix}`));
1543
+ log2(kleur2.red(t().setup.verifyFailed(check.reason ?? "")));
1544
+ log2(kleur2.dim(t().setup.notSaved(spec.fix)));
1222
1545
  return true;
1223
1546
  }
1224
1547
  saveCiProviderConfig(cfg);
1225
- log2(kleur2.green(` \u2705 ${spec.label} \uC5F0\uACB0\uB428${check.login ? ` (${check.login})` : ""} \u2192 ~/.mimi-seed/ci.json`));
1548
+ log2(kleur2.green(t().setup.ciSaved(credLabel(spec), check.login ? ` (${check.login})` : "")));
1226
1549
  return true;
1227
1550
  }
1228
1551
  case "command": {
1229
- log2(kleur2.yellow(` \uC774\uAC74 \uBCC4\uB3C4 \uBA85\uB839\uC73C\uB85C \uC2E4\uD589\uD574\uC918: ${kleur2.cyan(spec.setup.run)}`));
1230
- await ask(kleur2.dim(" (\uC5D4\uD130\uB97C \uB204\uB974\uBA74 \uACC4\uC18D) "));
1552
+ log2(kleur2.yellow(t().setup.runSeparately(kleur2.cyan(spec.setup.run))));
1553
+ await ask(kleur2.dim(t().setup.pressEnter));
1231
1554
  return true;
1232
1555
  }
1233
1556
  case "env": {
1234
- log2(kleur2.yellow(` \uD658\uACBD\uBCC0\uC218\uB85C \uC124\uC815\uD558\uB294 \uD56D\uBAA9\uC774\uC57C:`));
1557
+ log2(kleur2.yellow(t().setup.envVar));
1235
1558
  log2(kleur2.cyan(` export ${spec.setup.envVar}=...`));
1236
1559
  log2(kleur2.dim(` (Windows PowerShell: $env:${spec.setup.envVar} = "..." )`));
1237
- await ask(kleur2.dim(" (\uC5D4\uD130\uB97C \uB204\uB974\uBA74 \uACC4\uC18D) "));
1560
+ await ask(kleur2.dim(t().setup.pressEnter));
1238
1561
  return true;
1239
1562
  }
1240
1563
  }
@@ -1243,13 +1566,16 @@ async function cmdSetup(argv) {
1243
1566
  const opts = parseSetupArgs(argv);
1244
1567
  const home = os5.homedir();
1245
1568
  migrateLegacyJenkins(home);
1569
+ if (resolveMode(opts, process.env, process.stdin.isTTY) === "interactive") {
1570
+ await ensureLangChosen();
1571
+ }
1246
1572
  const hints = await detectHints(process.cwd());
1247
1573
  const platforms = opts.platforms ?? [
1248
1574
  hints.some((h) => h.packageName) ? "android" : null,
1249
1575
  hints.some((h) => h.bundleId) ? "ios" : null
1250
1576
  ].filter(Boolean);
1251
- log2(kleur2.bold("mimi-seed setup"));
1252
- if (platforms.length > 0) log2(kleur2.dim(` \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${platforms.join(", ")}`));
1577
+ log2(kleur2.bold(t().setup.title));
1578
+ if (platforms.length > 0) log2(kleur2.dim(t().setup.platformsDetected(platforms.join(", "))));
1253
1579
  log2();
1254
1580
  let detected = detectAll(home);
1255
1581
  printStatus(detected, platforms);
@@ -1262,53 +1588,54 @@ async function cmdSetup(argv) {
1262
1588
  if (mode === "report-only") {
1263
1589
  const missing = missingRequired(detected, platforms);
1264
1590
  if (missing.length > 0) {
1265
- log2(kleur2.yellow(" \uD544\uC218 \uD56D\uBAA9 \uB204\uB77D:"));
1266
- for (const spec of missing) log2(kleur2.yellow(` \u2022 ${spec.label} \u2192 ${spec.fix}`));
1591
+ log2(kleur2.yellow(t().setup.missingRequired));
1592
+ for (const spec of missing) log2(kleur2.yellow(` \u2022 ${credLabel(spec)} \u2192 ${spec.fix}`));
1267
1593
  log2();
1268
1594
  }
1269
1595
  if (opts.only && plan.length > 0) {
1270
- log2(kleur2.red(" \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:"));
1271
- for (const spec of plan) log2(kleur2.red(` \u2022 ${spec.label}`));
1272
- log2(kleur2.dim(" \uD130\uBBF8\uB110\uC5D0\uC11C \uC2E4\uD589\uD574\uC918 (Git Bash \uB4F1 TTY \uBBF8\uAC10\uC9C0 \uD658\uACBD\uC774\uBA74 --interactive)."));
1596
+ log2(kleur2.red(t().setup.cannotInteract));
1597
+ for (const spec of plan) log2(kleur2.red(` \u2022 ${credLabel(spec)}`));
1598
+ log2(kleur2.dim(t().setup.cannotInteractHint));
1273
1599
  process.exit(1);
1274
1600
  }
1275
- log2(kleur2.dim(" \uB300\uD654\uD615\uC73C\uB85C \uC5F0\uACB0\uD558\uB824\uBA74 \uD130\uBBF8\uB110\uC5D0\uC11C: mimi-seed setup"));
1601
+ log2(kleur2.dim(t().setup.runInTerminal));
1276
1602
  if (opts.failOnMissing && missing.length > 0) process.exit(1);
1277
1603
  return;
1278
1604
  }
1279
1605
  if (plan.length === 0) {
1280
1606
  if (opts.only) {
1281
- log2(kleur2.green(" \u2705 \uC694\uCCAD\uD55C \uD56D\uBAA9\uC740 \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4."));
1282
- log2(kleur2.dim(" \uB2E4\uC2DC \uC124\uC815\uD558\uB824\uBA74: mimi-seed setup --reconnect <id>"));
1607
+ log2(kleur2.green(t().setup.onlyAlreadyDone));
1608
+ log2(kleur2.dim(t().setup.onlyReconnectHint));
1283
1609
  } else {
1284
- log2(kleur2.green(" \u2705 \uC5F0\uACB0\uD560 \uAC8C \uB354 \uC5C6\uC5B4. \uB2E4 \uB410\uB2E4."));
1610
+ log2(kleur2.green(t().setup.allDone));
1285
1611
  }
1286
- log2(kleur2.dim(" \uC810\uAC80: mimi-seed doctor"));
1612
+ log2(kleur2.dim(" " + t().common.checkWith));
1287
1613
  if (opts.failOnMissing && missingRequired(detected, platforms).length > 0) process.exit(1);
1288
1614
  return;
1289
1615
  }
1290
- log2(kleur2.dim(` ${plan.length}\uAC1C \uD56D\uBAA9\uC744 \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCFC\uAC8C. \uC5B8\uC81C\uB4E0 s=\uAC74\uB108\uB6F0\uAE30, q=\uC885\uB8CC.`));
1616
+ log2(kleur2.dim(t().setup.planCount(plan.length)));
1291
1617
  log2();
1292
1618
  let aborted = false;
1293
1619
  for (const spec of plan) {
1294
1620
  if (aborted) break;
1295
- const req = spec.requirement === "optional" ? kleur2.dim("(\uC120\uD0DD)") : spec.requirement === "platform" ? kleur2.dim(`(${spec.platform} \uBC30\uD3EC\uC5D0 \uD544\uC694)`) : kleur2.red("(\uD544\uC218)");
1621
+ 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
1622
  for (; ; ) {
1297
- log2(kleur2.bold(`\u25B8 ${spec.label} ${req}`));
1298
- if (spec.note) log2(kleur2.dim(` ${spec.note}`));
1299
- const answer = (await ask(" [c] \uC5F0\uACB0 [s] \uAC74\uB108\uB6F0\uAE30 [?] \uC774\uAC74 \uC5B4\uB5BB\uAC8C \uAD6C\uD558\uB098\uC694 [q] \uC885\uB8CC : ")).toLowerCase();
1623
+ log2(kleur2.bold(`\u25B8 ${credLabel(spec)} ${req}`));
1624
+ const note = credNote(spec);
1625
+ if (note) log2(kleur2.dim(` ${note}`));
1626
+ const answer = (await ask(t().setup.prompt)).toLowerCase();
1300
1627
  if (answer === "?" || answer === "h") {
1301
1628
  printObtain(spec);
1302
1629
  continue;
1303
1630
  }
1304
1631
  if (answer === "q") {
1305
1632
  log2();
1306
- log2(kleur2.dim(" \uC911\uB2E8\uD588\uC5B4. \uC774\uC5B4\uC11C \uD558\uB824\uBA74 \uB2E4\uC2DC: mimi-seed setup"));
1633
+ log2(kleur2.dim(t().setup.quit));
1307
1634
  aborted = true;
1308
1635
  break;
1309
1636
  }
1310
1637
  if (answer === "s" || answer === "") {
1311
- log2(kleur2.dim(` \uAC74\uB108\uB700. \uB098\uC911\uC5D0: ${spec.fix}`));
1638
+ log2(kleur2.dim(t().setup.skipped(spec.fix)));
1312
1639
  log2();
1313
1640
  break;
1314
1641
  }
@@ -1317,7 +1644,7 @@ async function cmdSetup(argv) {
1317
1644
  log2();
1318
1645
  break;
1319
1646
  }
1320
- log2(kleur2.dim(" c / s / ? / q \uC911\uC5D0\uC11C \uACE8\uB77C\uC918."));
1647
+ log2(kleur2.dim(t().setup.promptInvalid));
1321
1648
  }
1322
1649
  }
1323
1650
  detected = detectAll(home);
@@ -1325,12 +1652,12 @@ async function cmdSetup(argv) {
1325
1652
  printStatus(detected, platforms);
1326
1653
  const stillMissing = missingRequired(detected, platforms);
1327
1654
  if (stillMissing.length === 0) {
1328
- log2(kleur2.green(" \u2705 \uD544\uC218 \uC5F0\uACB0 \uC644\uB8CC."));
1655
+ log2(kleur2.green(t().setup.requiredDone));
1329
1656
  } else {
1330
- log2(kleur2.yellow(" \uC544\uC9C1 \uD544\uC218 \uD56D\uBAA9\uC774 \uB0A8\uC544 \uC788\uC5B4:"));
1331
- for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${spec.label} \u2192 ${spec.fix}`));
1657
+ log2(kleur2.yellow(t().setup.stillMissing));
1658
+ for (const spec of stillMissing) log2(kleur2.yellow(` \u2022 ${credLabel(spec)} \u2192 ${spec.fix}`));
1332
1659
  }
1333
- log2(kleur2.dim(" \uC810\uAC80: mimi-seed doctor \xB7 \uBC30\uD3EC: mimi-seed deploy"));
1660
+ log2(kleur2.dim(t().setup.nextSteps));
1334
1661
  log2();
1335
1662
  if (opts.failOnMissing && stillMissing.length > 0) process.exit(1);
1336
1663
  }
@@ -1342,6 +1669,8 @@ export {
1342
1669
  deleteConfig,
1343
1670
  CONFIG_LOCATION,
1344
1671
  getEffectiveConfig,
1672
+ credLabel,
1673
+ credNote,
1345
1674
  CREDENTIALS,
1346
1675
  tryCredById,
1347
1676
  detectAll,