@modootoday/datalab-extension-mcp 1.2.5

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.
@@ -0,0 +1,1471 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // ../mcp-installer/dist/index.js
4
+ import { spawn as nodeSpawn } from "child_process";
5
+ import { promises as fs } from "fs";
6
+ import { homedir } from "os";
7
+ import { createInterface } from "readline";
8
+ var ART = [
9
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
10
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
11
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
12
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
13
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
14
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D",
15
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \uB370\uC774\uD130\uB7A9\uD234\uC988 \xB7 datalab.tools"
16
+ ];
17
+ function printBanner(out, subtitle) {
18
+ out("");
19
+ for (const row of ART) {
20
+ out(row);
21
+ }
22
+ out("");
23
+ out(` [\uB370\uC774\uD130\uB7A9\uD234\uC988] ${subtitle}`);
24
+ out("");
25
+ }
26
+ var INSTALL_SUBTITLE = "\uCEE4\uB125\uD130 \uC124\uCE58 \uB3C4\uC6B0\uBBF8";
27
+ var UNINSTALL_SUBTITLE = "\uCEE4\uB125\uD130 \uC815\uB9AC \uB3C4\uC6B0\uBBF8";
28
+ var SERVER_NAME = "datalab";
29
+ var SERVER_PACKAGE = "@modootoday/datalab-extension-mcp";
30
+ var DEFAULT_PORT = "8765";
31
+ var DEFAULT_EXTENSION_ID = "ldoknfkedngbdfgdkeicojmhnojgpdcb";
32
+ function packageSpec(version) {
33
+ return `${SERVER_PACKAGE}@${version}`;
34
+ }
35
+ function isCustomPort(port) {
36
+ if (port === void 0 || port === "") {
37
+ return false;
38
+ }
39
+ return port !== DEFAULT_PORT;
40
+ }
41
+ function buildEnv(opts) {
42
+ const env = {
43
+ DATALAB_MCP_TOKEN: opts.token,
44
+ DATALAB_MCP_EXTENSION_ID: opts.extensionId
45
+ };
46
+ if (isCustomPort(opts.port)) {
47
+ env["DATALAB_MCP_PORT"] = opts.port;
48
+ }
49
+ return env;
50
+ }
51
+ function daemonMcpUrl(opts) {
52
+ const port = isCustomPort(opts.port) ? opts.port : DEFAULT_PORT;
53
+ return `http://127.0.0.1:${port}/mcp`;
54
+ }
55
+ function buildUrlEntry(opts) {
56
+ return { url: daemonMcpUrl(opts) };
57
+ }
58
+ function buildFileEntry(opts, platform) {
59
+ const spec = packageSpec(opts.version);
60
+ if (platform === "win32") {
61
+ return {
62
+ command: "cmd",
63
+ args: ["/c", "npx", "-y", spec],
64
+ env: buildEnv(opts)
65
+ };
66
+ }
67
+ return {
68
+ command: "npx",
69
+ args: ["-y", spec],
70
+ env: buildEnv(opts)
71
+ };
72
+ }
73
+ var CODEX_CHATGPT_NOTE = "ChatGPT \uB370\uC2A4\uD06C\uD1B1\uACFC \uD568\uAED8 \uC5F0\uACB0\uB3FC\uC694.";
74
+ function envPairs(opts) {
75
+ const pairs = [
76
+ `DATALAB_MCP_TOKEN=${opts.token}`,
77
+ `DATALAB_MCP_EXTENSION_ID=${opts.extensionId}`
78
+ ];
79
+ if (isCustomPort(opts.port)) {
80
+ pairs.push(`DATALAB_MCP_PORT=${opts.port}`);
81
+ }
82
+ return pairs;
83
+ }
84
+ var CLI_HOSTS = [
85
+ {
86
+ id: "claude",
87
+ tier: 1,
88
+ displayName: "Claude Code",
89
+ bin: "claude",
90
+ // --scope user is mandatory: the default "local" scope binds the server to
91
+ // whatever directory the user happened to paste the command in, which
92
+ // looks like a success and then silently does nothing everywhere else.
93
+ buildAddArgs(opts) {
94
+ const args = ["mcp", "add", SERVER_NAME, "--scope", "user"];
95
+ for (const pair of envPairs(opts)) {
96
+ args.push("--env", pair);
97
+ }
98
+ args.push("--", "npx", "-y", packageSpec(opts.version));
99
+ return args;
100
+ },
101
+ buildRemoveArgs() {
102
+ return ["mcp", "remove", SERVER_NAME, "--scope", "user"];
103
+ }
104
+ },
105
+ {
106
+ id: "codex",
107
+ tier: 1,
108
+ displayName: "ChatGPT \uB370\uC2A4\uD06C\uD1B1 / Codex",
109
+ bin: "codex",
110
+ // The Codex CLI and the ChatGPT desktop app share one config, so a single
111
+ // registration covers both — worth saying out loud in the output.
112
+ note: CODEX_CHATGPT_NOTE,
113
+ buildAddArgs(opts) {
114
+ const args = ["mcp", "add", SERVER_NAME];
115
+ for (const pair of envPairs(opts)) {
116
+ args.push("--env", pair);
117
+ }
118
+ args.push("--", "npx", "-y", packageSpec(opts.version));
119
+ return args;
120
+ },
121
+ buildRemoveArgs() {
122
+ return ["mcp", "remove", SERVER_NAME];
123
+ }
124
+ },
125
+ {
126
+ id: "gemini",
127
+ tier: 1,
128
+ displayName: "Gemini CLI",
129
+ bin: "gemini",
130
+ // -s user for the same cwd-binding reason as Claude Code's --scope user.
131
+ buildAddArgs(opts) {
132
+ const args = ["mcp", "add", "-s", "user"];
133
+ for (const pair of envPairs(opts)) {
134
+ args.push("-e", pair);
135
+ }
136
+ args.push(SERVER_NAME, "npx", "-y", packageSpec(opts.version));
137
+ return args;
138
+ },
139
+ buildRemoveArgs() {
140
+ return ["mcp", "remove", "-s", "user", SERVER_NAME];
141
+ }
142
+ }
143
+ ];
144
+ var INSTALLABLE_CLIS = [
145
+ {
146
+ id: "claude",
147
+ displayName: "Claude Code",
148
+ npmPackage: "@anthropic-ai/claude-code"
149
+ },
150
+ { id: "gemini", displayName: "Gemini CLI", npmPackage: "@google/gemini-cli" },
151
+ {
152
+ id: "codex",
153
+ displayName: "ChatGPT \uB370\uC2A4\uD06C\uD1B1 / Codex",
154
+ npmPackage: "@openai/codex"
155
+ }
156
+ ];
157
+ function joinPath(io, ...parts) {
158
+ if (io.platform === "win32") {
159
+ return parts.join("\\");
160
+ }
161
+ return parts.join("/");
162
+ }
163
+ var FILE_HOSTS = [
164
+ {
165
+ id: "claude-desktop",
166
+ tier: 2,
167
+ displayName: "Claude Desktop",
168
+ configKey: "mcpServers",
169
+ entryKind: "stdio",
170
+ configPath(io) {
171
+ if (io.platform === "darwin") {
172
+ return joinPath(
173
+ io,
174
+ io.homedir(),
175
+ "Library",
176
+ "Application Support",
177
+ "Claude",
178
+ "claude_desktop_config.json"
179
+ );
180
+ }
181
+ if (io.platform === "win32") {
182
+ const appData = io.env["APPDATA"];
183
+ if (appData === void 0 || appData === "") {
184
+ return null;
185
+ }
186
+ return joinPath(io, appData, "Claude", "claude_desktop_config.json");
187
+ }
188
+ return null;
189
+ }
190
+ },
191
+ {
192
+ id: "cursor",
193
+ tier: 2,
194
+ displayName: "Cursor",
195
+ configKey: "mcpServers",
196
+ // Cursor speaks local HTTP MCP, so it connects to the daemon directly
197
+ // (url entry) — no adapter process spawned for it.
198
+ entryKind: "url",
199
+ configPath(io) {
200
+ return joinPath(io, io.homedir(), ".cursor", "mcp.json");
201
+ }
202
+ },
203
+ {
204
+ // Windsurf's config is documented strict JSON with an `mcpServers` map, so
205
+ // it graduates from a printed snippet to a safe single-key merge. The
206
+ // hygiene floor still refuses (and falls back to a snippet) if a given
207
+ // install turns out to carry comments, so the promotion cannot corrupt.
208
+ id: "windsurf",
209
+ tier: 2,
210
+ displayName: "Windsurf",
211
+ configKey: "mcpServers",
212
+ entryKind: "stdio",
213
+ configPath(io) {
214
+ return joinPath(
215
+ io,
216
+ io.homedir(),
217
+ ".codeium",
218
+ "windsurf",
219
+ "mcp_config.json"
220
+ );
221
+ }
222
+ },
223
+ {
224
+ // Amazon Q Developer's global MCP config is strict JSON at a fixed,
225
+ // home-relative path on every OS. We merge the file directly rather than
226
+ // shelling out to `q mcp add`, because the file path gives us a clean
227
+ // symmetric uninstall without depending on a remove subcommand we have
228
+ // not verified.
229
+ id: "amazon-q",
230
+ tier: 2,
231
+ displayName: "Amazon Q Developer",
232
+ configKey: "mcpServers",
233
+ entryKind: "stdio",
234
+ configPath(io) {
235
+ return joinPath(io, io.homedir(), ".aws", "amazonq", "mcp.json");
236
+ }
237
+ },
238
+ {
239
+ // JetBrains Junie — documented strict-JSON user config, home-relative on
240
+ // every OS.
241
+ id: "junie",
242
+ tier: 2,
243
+ displayName: "JetBrains Junie",
244
+ configKey: "mcpServers",
245
+ entryKind: "stdio",
246
+ configPath(io) {
247
+ return joinPath(io, io.homedir(), ".junie", "mcp", "mcp.json");
248
+ }
249
+ },
250
+ {
251
+ // Kiro — documented strict-JSON user settings, home-relative on every OS.
252
+ id: "kiro",
253
+ tier: 2,
254
+ displayName: "Kiro",
255
+ configKey: "mcpServers",
256
+ entryKind: "stdio",
257
+ configPath(io) {
258
+ return joinPath(io, io.homedir(), ".kiro", "settings", "mcp.json");
259
+ }
260
+ }
261
+ ];
262
+ function buildEntryForHost(host, opts, platform) {
263
+ if (host.entryKind === "url") {
264
+ return buildUrlEntry(opts);
265
+ }
266
+ return buildFileEntry(opts, platform);
267
+ }
268
+ function jsonSnippet(topKey, opts, platform) {
269
+ const entry = buildFileEntry(opts, platform);
270
+ return JSON.stringify({ [topKey]: { [SERVER_NAME]: entry } }, null, 2);
271
+ }
272
+ function vscodeUserDir(io) {
273
+ if (io.platform === "darwin") {
274
+ return joinPath(
275
+ io,
276
+ io.homedir(),
277
+ "Library",
278
+ "Application Support",
279
+ "Code",
280
+ "User"
281
+ );
282
+ }
283
+ if (io.platform === "win32") {
284
+ const appData = io.env["APPDATA"];
285
+ if (appData === void 0 || appData === "") {
286
+ return null;
287
+ }
288
+ return joinPath(io, appData, "Code", "User");
289
+ }
290
+ return joinPath(io, io.homedir(), ".config", "Code", "User");
291
+ }
292
+ function vscodeConfigPath(io) {
293
+ const dir = vscodeUserDir(io);
294
+ if (dir === null) {
295
+ return null;
296
+ }
297
+ return joinPath(io, dir, "mcp.json");
298
+ }
299
+ function vscodeExtSettingsPath(io, ext, file) {
300
+ const dir = vscodeUserDir(io);
301
+ if (dir === null) {
302
+ return null;
303
+ }
304
+ return joinPath(io, dir, "globalStorage", ext, "settings", file);
305
+ }
306
+ async function fileOrParentExists(io, path) {
307
+ if (await io.exists(path)) {
308
+ return true;
309
+ }
310
+ const cut = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
311
+ if (cut <= 0) {
312
+ return false;
313
+ }
314
+ return io.exists(path.slice(0, cut));
315
+ }
316
+ function tomlSnippet(opts) {
317
+ const lines = [
318
+ `[mcp_servers.${SERVER_NAME}]`,
319
+ `command = "npx"`,
320
+ `args = ["-y", "${packageSpec(opts.version)}"]`,
321
+ "",
322
+ `[mcp_servers.${SERVER_NAME}.env]`
323
+ ];
324
+ for (const [key, value] of Object.entries(buildEnv(opts))) {
325
+ lines.push(`${key} = "${value}"`);
326
+ }
327
+ return lines.join("\n");
328
+ }
329
+ var SNIPPET_HOSTS = [
330
+ {
331
+ id: "vscode",
332
+ tier: 3,
333
+ displayName: "VS Code",
334
+ async detect(io) {
335
+ const path = vscodeConfigPath(io);
336
+ if (path === null) {
337
+ return false;
338
+ }
339
+ return fileOrParentExists(io, path);
340
+ },
341
+ detectedPath(io) {
342
+ return vscodeConfigPath(io);
343
+ },
344
+ reason: "\uC8FC\uC11D\uC774 \uC788\uB294 \uC124\uC815 \uD30C\uC77C\uC774\uB77C \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
345
+ pasteWhere: '\uC544\uB798 \uB0B4\uC6A9\uC744 \uC124\uC815 \uD30C\uC77C\uC758 "servers" \uD56D\uBAA9\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.',
346
+ buildSnippet(opts, platform) {
347
+ return jsonSnippet("servers", opts, platform);
348
+ }
349
+ },
350
+ {
351
+ id: "zed",
352
+ tier: 3,
353
+ displayName: "Zed",
354
+ async detect(io) {
355
+ return fileOrParentExists(
356
+ io,
357
+ joinPath(io, io.homedir(), ".config", "zed", "settings.json")
358
+ );
359
+ },
360
+ detectedPath(io) {
361
+ return joinPath(io, io.homedir(), ".config", "zed", "settings.json");
362
+ },
363
+ reason: "\uC8FC\uC11D\uC774 \uC788\uB294 \uC124\uC815 \uD30C\uC77C\uC774\uB77C \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
364
+ pasteWhere: '\uC544\uB798 \uB0B4\uC6A9\uC744 \uC124\uC815 \uD30C\uC77C\uC758 "context_servers" \uD56D\uBAA9\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.',
365
+ buildSnippet(opts, platform) {
366
+ return jsonSnippet("context_servers", opts, platform);
367
+ }
368
+ },
369
+ {
370
+ // Cline (VS Code extension). Its settings file is machine-written strict
371
+ // JSON with an `mcpServers` map, but it sits under VS Code's per-variant
372
+ // globalStorage dir; rather than auto-write a path that shifts with the
373
+ // editor variant, we detect the stable-"Code" location and print a snippet.
374
+ id: "cline",
375
+ tier: 3,
376
+ displayName: "Cline (VS Code)",
377
+ async detect(io) {
378
+ const path = vscodeExtSettingsPath(
379
+ io,
380
+ "saoudrizwan.claude-dev",
381
+ "cline_mcp_settings.json"
382
+ );
383
+ if (path === null) {
384
+ return false;
385
+ }
386
+ return fileOrParentExists(io, path);
387
+ },
388
+ detectedPath(io) {
389
+ return vscodeExtSettingsPath(
390
+ io,
391
+ "saoudrizwan.claude-dev",
392
+ "cline_mcp_settings.json"
393
+ );
394
+ },
395
+ reason: "\uC124\uC815 \uD30C\uC77C \uC704\uCE58\uAC00 \uD3B8\uC9D1\uAE30 \uBC84\uC804\uB9C8\uB2E4 \uB2EC\uB77C\uC11C \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
396
+ pasteWhere: '\uC544\uB798 \uB0B4\uC6A9\uC744 \uC124\uC815 \uD30C\uC77C\uC758 "mcpServers" \uD56D\uBAA9\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.',
397
+ buildSnippet(opts, platform) {
398
+ return jsonSnippet("mcpServers", opts, platform);
399
+ }
400
+ },
401
+ {
402
+ // Roo Code (VS Code extension) — same globalStorage story as Cline.
403
+ id: "roo-code",
404
+ tier: 3,
405
+ displayName: "Roo Code (VS Code)",
406
+ async detect(io) {
407
+ const path = vscodeExtSettingsPath(
408
+ io,
409
+ "rooveterinaryinc.roo-cline",
410
+ "mcp_settings.json"
411
+ );
412
+ if (path === null) {
413
+ return false;
414
+ }
415
+ return fileOrParentExists(io, path);
416
+ },
417
+ detectedPath(io) {
418
+ return vscodeExtSettingsPath(
419
+ io,
420
+ "rooveterinaryinc.roo-cline",
421
+ "mcp_settings.json"
422
+ );
423
+ },
424
+ reason: "\uC124\uC815 \uD30C\uC77C \uC704\uCE58\uAC00 \uD3B8\uC9D1\uAE30 \uBC84\uC804\uB9C8\uB2E4 \uB2EC\uB77C\uC11C \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
425
+ pasteWhere: '\uC544\uB798 \uB0B4\uC6A9\uC744 \uC124\uC815 \uD30C\uC77C\uC758 "mcpServers" \uD56D\uBAA9\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.',
426
+ buildSnippet(opts, platform) {
427
+ return jsonSnippet("mcpServers", opts, platform);
428
+ }
429
+ },
430
+ {
431
+ // LM Studio — same `mcpServers` shape ("Cursor notation"). The exact path
432
+ // is only medium-confidence, so it stays a snippet: a wrong detect just
433
+ // means no card (safe), where a wrong auto-write would be a false success.
434
+ id: "lmstudio",
435
+ tier: 3,
436
+ displayName: "LM Studio",
437
+ async detect(io) {
438
+ return fileOrParentExists(
439
+ io,
440
+ joinPath(io, io.homedir(), ".lmstudio", "mcp.json")
441
+ );
442
+ },
443
+ detectedPath(io) {
444
+ return joinPath(io, io.homedir(), ".lmstudio", "mcp.json");
445
+ },
446
+ reason: "\uC124\uC815 \uD30C\uC77C \uACBD\uB85C\uAC00 \uACF5\uC2DD\uC801\uC73C\uB85C \uD655\uC815\uB418\uC9C0 \uC54A\uC544 \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
447
+ pasteWhere: '\uC544\uB798 \uB0B4\uC6A9\uC744 \uC124\uC815 \uD30C\uC77C\uC758 "mcpServers" \uD56D\uBAA9\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.',
448
+ buildSnippet(opts, platform) {
449
+ return jsonSnippet("mcpServers", opts, platform);
450
+ }
451
+ },
452
+ {
453
+ // Warp terminal — same `mcpServers` shape at `~/.warp/.mcp.json`
454
+ // (medium-confidence path → snippet, not auto-write).
455
+ id: "warp",
456
+ tier: 3,
457
+ displayName: "Warp",
458
+ async detect(io) {
459
+ return fileOrParentExists(
460
+ io,
461
+ joinPath(io, io.homedir(), ".warp", ".mcp.json")
462
+ );
463
+ },
464
+ detectedPath(io) {
465
+ return joinPath(io, io.homedir(), ".warp", ".mcp.json");
466
+ },
467
+ reason: "\uC124\uC815 \uD30C\uC77C \uACBD\uB85C\uAC00 \uACF5\uC2DD\uC801\uC73C\uB85C \uD655\uC815\uB418\uC9C0 \uC54A\uC544 \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
468
+ pasteWhere: '\uC544\uB798 \uB0B4\uC6A9\uC744 \uC124\uC815 \uD30C\uC77C\uC758 "mcpServers" \uD56D\uBAA9\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.',
469
+ buildSnippet(opts, platform) {
470
+ return jsonSnippet("mcpServers", opts, platform);
471
+ }
472
+ },
473
+ {
474
+ id: "claude-desktop-linux",
475
+ tier: 3,
476
+ displayName: "Claude Desktop (Linux)",
477
+ async detect(io) {
478
+ if (io.platform !== "linux") {
479
+ return false;
480
+ }
481
+ return io.exists(joinPath(io, io.homedir(), ".config", "Claude"));
482
+ },
483
+ detectedPath(io) {
484
+ return joinPath(io, io.homedir(), ".config", "Claude");
485
+ },
486
+ // Guessing a path here would manufacture false successes that only
487
+ // surface after a restart — the worst possible failure mode.
488
+ reason: "\uB9AC\uB205\uC2A4\uC6A9 \uACF5\uC2DD \uC124\uC815 \uD30C\uC77C \uACBD\uB85C\uAC00 \uD655\uC778\uB418\uC9C0 \uC54A\uC544 \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
489
+ pasteWhere: "Claude Desktop \uACF5\uC2DD \uBB38\uC11C\uAC00 \uC548\uB0B4\uD558\uB294 \uC124\uC815 \uD30C\uC77C(claude_desktop_config.json)\uC5D0 \uC544\uB798 \uB0B4\uC6A9\uC744 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.",
490
+ buildSnippet(opts, platform) {
491
+ return jsonSnippet("mcpServers", opts, platform);
492
+ }
493
+ },
494
+ {
495
+ id: "codex-config-only",
496
+ tier: 3,
497
+ displayName: "ChatGPT \uB370\uC2A4\uD06C\uD1B1 / Codex (\uC124\uC815 \uD30C\uC77C\uB9CC \uBC1C\uACAC)",
498
+ async detect(io, ctx) {
499
+ if (ctx.cliDetected.has("codex")) {
500
+ return false;
501
+ }
502
+ return io.exists(joinPath(io, io.homedir(), ".codex", "config.toml"));
503
+ },
504
+ detectedPath(io) {
505
+ return joinPath(io, io.homedir(), ".codex", "config.toml");
506
+ },
507
+ reason: "Codex \uBA85\uB839\uC5B4(CLI)\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC544 TOML \uC124\uC815 \uD30C\uC77C\uC744 \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694.",
508
+ pasteWhere: "\uC544\uB798 \uB0B4\uC6A9\uC744 \uC704 \uC124\uC815 \uD30C\uC77C\uC5D0 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.",
509
+ buildSnippet(opts) {
510
+ return tomlSnippet(opts);
511
+ }
512
+ }
513
+ ];
514
+ var SUPPORTED_APPS = [
515
+ { name: "Claude Desktop", url: "https://claude.ai/download" },
516
+ { name: "Claude Code", url: "https://claude.com/claude-code" },
517
+ { name: "ChatGPT \uB370\uC2A4\uD06C\uD1B1", url: "https://openai.com/chatgpt/download/" },
518
+ { name: "Codex CLI", url: "https://developers.openai.com/codex/" },
519
+ { name: "Gemini CLI", url: "https://github.com/google-gemini/gemini-cli" },
520
+ { name: "Cursor", url: "https://cursor.com/downloads" },
521
+ { name: "Windsurf", url: "https://windsurf.com/download" },
522
+ { name: "Amazon Q Developer", url: "https://aws.amazon.com/q/developer/" },
523
+ { name: "JetBrains Junie", url: "https://www.jetbrains.com/junie/" },
524
+ { name: "Kiro", url: "https://kiro.dev/" },
525
+ { name: "VS Code", url: "https://code.visualstudio.com/" },
526
+ { name: "Zed", url: "https://zed.dev/download" },
527
+ { name: "Cline", url: "https://cline.bot/" },
528
+ { name: "Roo Code", url: "https://roocode.com/" },
529
+ { name: "LM Studio", url: "https://lmstudio.ai/" },
530
+ { name: "Warp", url: "https://www.warp.dev/" }
531
+ ];
532
+ var RESTART_NOTICE = "\uB9C8\uC9C0\uB9C9 \uD55C \uB2E8\uACC4: AI \uC571\uC744 \uC644\uC804\uD788 \uC885\uB8CC\uD588\uB2E4\uAC00 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.\n(Windows: \uC791\uC5C5 \uD45C\uC2DC\uC904 \uD2B8\uB808\uC774 \uC544\uC774\uCF58\uC5D0\uC11C \uC885\uB8CC)";
533
+ var NOTHING_CHANGED = "\uC544\uBB34\uAC83\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uC558\uC5B4\uC694.";
534
+ var UNINSTALL_DONE = "\uC815\uB9AC\uAC00 \uB05D\uB0AC\uC5B4\uC694.";
535
+ var UNINSTALL_TOKEN_REMINDER = "\uC5F0\uACB0 \uD1A0\uD070\uAE4C\uC9C0 \uC5C6\uC560\uB824\uBA74 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC5D0\uC11C '\uC5F0\uB3D9 \uD574\uC81C' \uBC84\uD2BC\uC744 \uB20C\uB7EC \uC8FC\uC138\uC694. \uD3D0\uAE30\uB41C \uD1A0\uD070\uC740 \uC124\uC815\uC5D0 \uB0A8\uC740 \uD56D\uBAA9\uC744 \uC804\uBD80 \uBB34\uB825\uD654\uD574\uC694.";
536
+ var PERMISSION_DENIED_HINT = "\uD30C\uC77C \uAD8C\uD55C \uB54C\uBB38\uC5D0 \uC124\uC815\uC744 \uC4F8 \uC218 \uC5C6\uC5C8\uC5B4\uC694. \uD30C\uC77C \uAD8C\uD55C\uC744 \uD655\uC778\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
537
+ var NO_HOSTS_DETECTED = "\uC5F0\uACB0\uD560 \uC218 \uC788\uB294 AI \uD504\uB85C\uADF8\uB7A8\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC5B4\uC694.";
538
+ var SUPPORTED_APPS_HEADER = "\uC9C0\uC6D0\uD558\uB294 \uD504\uB85C\uADF8\uB7A8\uACFC \uB2E4\uC6B4\uB85C\uB4DC \uC8FC\uC18C\uC608\uC694:";
539
+ var AFTER_INSTALL_RETRY = "\uD504\uB85C\uADF8\uB7A8\uC744 \uC124\uCE58\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
540
+ var TOKEN_PROMPT_GUIDE = "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0' \uCE74\uB4DC\uC758 \uC5F0\uACB0 \uD1A0\uD070\uC744 \uBCF5\uC0AC\uD574 \uC8FC\uC138\uC694.";
541
+ var TOKEN_PROMPT_QUESTION = "\uBCF5\uC0AC\uD55C \uC5F0\uACB0 \uD1A0\uD070\uC744 \uC5EC\uAE30\uC5D0 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694:";
542
+ var TOKEN_PROMPT_RETRY = "\uD1A0\uD070 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. \uCE74\uB4DC\uC758 \uD1A0\uD070\uC744 \uB2E4\uC2DC \uBCF5\uC0AC\uD574\uC11C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.";
543
+ var TOKEN_REQUIRED_NON_INTERACTIVE = "\uC5F0\uACB0 \uD1A0\uD070\uC774 \uD544\uC694\uD574\uC694. \uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0' \uCE74\uB4DC\uC758 [\uC124\uCE58 \uBA85\uB839 \uBCF5\uC0AC] \uBC84\uD2BC\uC744 \uB20C\uB7EC, \uBCF5\uC0AC\uB41C \uBA85\uB839\uC744 \uADF8\uB300\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
544
+ var CLI_OFFER_INTRO = "\uC6D0\uD558\uC2DC\uBA74 \uC544\uB798 AI \uD504\uB85C\uADF8\uB7A8 \uC911 \uD558\uB098\uB97C \uC9C0\uAE08 \uBC14\uB85C \uC124\uCE58\uD560 \uC218 \uC788\uC5B4\uC694. Node.js \uAE30\uBC18\uC774\uB77C \uC774 \uC790\uB9AC\uC5D0\uC11C \uC124\uCE58\uB3FC\uC694 \u2014 \uC548 \uD558\uC154\uB3C4 \uAD1C\uCC2E\uC544\uC694.";
545
+ var CLI_OFFER_SKIP_LABEL = "0. \uC9C0\uAE08\uC740 \uC124\uCE58\uD558\uC9C0 \uC54A\uAE30";
546
+ var CLI_OFFER_QUESTION = "\uC124\uCE58\uD560 \uD504\uB85C\uADF8\uB7A8\uC758 \uBC88\uD638\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694";
547
+ function cliInstalling(name, pkg) {
548
+ return `${name}\uC744(\uB97C) \uC124\uCE58\uD558\uACE0 \uC788\uC5B4\uC694... (npm install -g ${pkg})`;
549
+ }
550
+ function cliInstalled(name) {
551
+ return `${name} \uC124\uCE58\uB97C \uB9C8\uCCE4\uC5B4\uC694.`;
552
+ }
553
+ function cliInstallFailed(name) {
554
+ return `${name} \uC124\uCE58\uAC00 \uC2E4\uD328\uD588\uC5B4\uC694. \uC7A0\uC2DC \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098 \uC9C1\uC811 \uC124\uCE58\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC744 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.`;
555
+ }
556
+ function cliInstalledRetry(name) {
557
+ return `${name}\uC744(\uB97C) \uC124\uCE58\uD588\uC5B4\uC694. \uD130\uBBF8\uB110\uC744 \uC0C8\uB85C \uC5F4\uACE0 \uAC19\uC740 \uBA85\uB839\uC744 \uD55C \uBC88 \uB354 \uC2E4\uD589\uD558\uBA74 \uC5F0\uACB0\uB3FC\uC694.`;
558
+ }
559
+ function installQuestion(count) {
560
+ return `\uC704 ${count}\uAC1C \uD504\uB85C\uADF8\uB7A8\uC5D0 \uC5F0\uACB0\uD560\uAE4C\uC694?`;
561
+ }
562
+ function uninstallQuestion(count) {
563
+ return `\uC704 ${count}\uAC1C \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C \uC5F0\uACB0\uC744 \uD574\uC81C\uD560\uAE4C\uC694?`;
564
+ }
565
+ var TOKEN_RE = /^[0-9a-f]{32,128}$/i;
566
+ var EXTENSION_ID_RE = /^[a-p]{32}$/;
567
+ var PORT_RE = /^\d{2,5}$/;
568
+ var VERSION_RE = /^\d+\.\d+\.\d+$/;
569
+ var RETRY_HINT = "\uD328\uB110\uC5D0\uC11C \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uBCF5\uC0AC\uD574\uC11C \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
570
+ var INVALID_TOKEN_MESSAGE = `\uC5F0\uACB0 \uD1A0\uD070 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. ${RETRY_HINT}`;
571
+ var INVALID_EXTENSION_ID_MESSAGE = `\uD655\uC7A5 \uD504\uB85C\uADF8\uB7A8 ID \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. ${RETRY_HINT}`;
572
+ var INVALID_PORT_MESSAGE = `\uD3EC\uD2B8 \uBC88\uD638 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. ${RETRY_HINT}`;
573
+ var INVALID_VERSION_MESSAGE = `\uBC84\uC804 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. ${RETRY_HINT}`;
574
+ function validateInstallOptions(opts) {
575
+ if (typeof opts.version !== "string" || !VERSION_RE.test(opts.version)) {
576
+ return INVALID_VERSION_MESSAGE;
577
+ }
578
+ if (typeof opts.token !== "string" || !TOKEN_RE.test(opts.token)) {
579
+ return INVALID_TOKEN_MESSAGE;
580
+ }
581
+ if (typeof opts.extensionId !== "string" || !EXTENSION_ID_RE.test(opts.extensionId)) {
582
+ return INVALID_EXTENSION_ID_MESSAGE;
583
+ }
584
+ if (opts.port !== void 0 && !PORT_RE.test(opts.port)) {
585
+ return INVALID_PORT_MESSAGE;
586
+ }
587
+ return null;
588
+ }
589
+ function validateUninstallOptions(opts) {
590
+ if (opts.version !== void 0 && opts.version !== "" && !VERSION_RE.test(opts.version)) {
591
+ return INVALID_VERSION_MESSAGE;
592
+ }
593
+ if (opts.token !== void 0 && !TOKEN_RE.test(opts.token)) {
594
+ return INVALID_TOKEN_MESSAGE;
595
+ }
596
+ if (opts.extensionId !== void 0 && !EXTENSION_ID_RE.test(opts.extensionId)) {
597
+ return INVALID_EXTENSION_ID_MESSAGE;
598
+ }
599
+ if (opts.port !== void 0 && !PORT_RE.test(opts.port)) {
600
+ return INVALID_PORT_MESSAGE;
601
+ }
602
+ return null;
603
+ }
604
+ var BACKUP_KEEP = 3;
605
+ function formatBackupTimestamp(date) {
606
+ const pad = (n) => String(n).padStart(2, "0");
607
+ return String(date.getUTCFullYear()) + pad(date.getUTCMonth() + 1) + pad(date.getUTCDate()) + pad(date.getUTCHours()) + pad(date.getUTCMinutes()) + pad(date.getUTCSeconds());
608
+ }
609
+ function splitPath(filePath) {
610
+ const cut = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
611
+ if (cut < 0) {
612
+ return { dir: ".", base: filePath };
613
+ }
614
+ return { dir: filePath.slice(0, cut), base: filePath.slice(cut + 1) };
615
+ }
616
+ function joinDir(dir, name) {
617
+ if (dir.includes("\\") && !dir.includes("/")) {
618
+ return `${dir}\\${name}`;
619
+ }
620
+ return `${dir}/${name}`;
621
+ }
622
+ function isPlainObject(value) {
623
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
624
+ return false;
625
+ }
626
+ return true;
627
+ }
628
+ async function readDoc(io, filePath) {
629
+ if (!await io.exists(filePath)) {
630
+ return { exists: false, raw: "", doc: null, parseError: false };
631
+ }
632
+ const raw = await io.readFile(filePath);
633
+ try {
634
+ const parsed = JSON.parse(raw);
635
+ if (!isPlainObject(parsed)) {
636
+ return { exists: true, raw, doc: null, parseError: true };
637
+ }
638
+ return { exists: true, raw, doc: parsed, parseError: false };
639
+ } catch {
640
+ return { exists: true, raw, doc: null, parseError: true };
641
+ }
642
+ }
643
+ function serialize(doc) {
644
+ return `${JSON.stringify(doc, null, 2)}
645
+ `;
646
+ }
647
+ async function atomicWrite(io, filePath, content) {
648
+ const tempPath = `${filePath}.tmp-${formatBackupTimestamp(io.now())}`;
649
+ await io.writeFile(tempPath, content);
650
+ await io.rename(tempPath, filePath);
651
+ }
652
+ async function writeBackup(io, filePath, raw) {
653
+ const { dir, base } = splitPath(filePath);
654
+ const timestamp = formatBackupTimestamp(io.now());
655
+ const backupPath = joinDir(dir, `${base}.backup-${timestamp}`);
656
+ await io.writeFile(backupPath, raw);
657
+ const prefix = `${base}.backup-`;
658
+ const names = await io.listBackups(dir);
659
+ const backups = names.filter((name) => {
660
+ if (!name.startsWith(prefix)) {
661
+ return false;
662
+ }
663
+ return /^\d{14}$/.test(name.slice(prefix.length));
664
+ }).sort();
665
+ const excess = backups.slice(0, Math.max(0, backups.length - BACKUP_KEEP));
666
+ for (const name of excess) {
667
+ await io.unlink(joinDir(dir, name));
668
+ }
669
+ return backupPath;
670
+ }
671
+ async function findNewestBackup(io, filePath) {
672
+ const { dir, base } = splitPath(filePath);
673
+ const prefix = `${base}.backup-`;
674
+ const names = await io.listBackups(dir);
675
+ const backups = names.filter((name) => {
676
+ if (!name.startsWith(prefix)) {
677
+ return false;
678
+ }
679
+ return /^\d{14}$/.test(name.slice(prefix.length));
680
+ }).sort();
681
+ const newest = backups[backups.length - 1];
682
+ if (newest === void 0) {
683
+ return null;
684
+ }
685
+ return joinDir(dir, newest);
686
+ }
687
+ async function restoreNewestBackup(io, filePath) {
688
+ const backupPath = await findNewestBackup(io, filePath);
689
+ if (backupPath === null) {
690
+ return false;
691
+ }
692
+ const raw = await io.readFile(backupPath);
693
+ await atomicWrite(io, filePath, raw);
694
+ return true;
695
+ }
696
+ function deepEqual(a, b) {
697
+ if (a === b) {
698
+ return true;
699
+ }
700
+ if (Array.isArray(a) && Array.isArray(b)) {
701
+ if (a.length !== b.length) {
702
+ return false;
703
+ }
704
+ return a.every((item, i) => deepEqual(item, b[i]));
705
+ }
706
+ if (isPlainObject(a) && isPlainObject(b)) {
707
+ const keysA = Object.keys(a);
708
+ const keysB = Object.keys(b);
709
+ if (keysA.length !== keysB.length) {
710
+ return false;
711
+ }
712
+ return keysA.every((key) => deepEqual(a[key], b[key]));
713
+ }
714
+ return false;
715
+ }
716
+ async function upsertServerKey(io, filePath, entry) {
717
+ const state = await readDoc(io, filePath);
718
+ if (state.parseError) {
719
+ return { ok: false, changed: false, reason: "parse" };
720
+ }
721
+ let doc;
722
+ if (state.doc !== null) {
723
+ doc = state.doc;
724
+ } else {
725
+ doc = {};
726
+ }
727
+ const existingServers = doc["mcpServers"];
728
+ let servers;
729
+ if (existingServers === void 0) {
730
+ servers = {};
731
+ } else if (isPlainObject(existingServers)) {
732
+ servers = existingServers;
733
+ } else {
734
+ return { ok: false, changed: false, reason: "parse" };
735
+ }
736
+ doc["mcpServers"] = servers;
737
+ servers[SERVER_NAME] = entry;
738
+ let backupPath;
739
+ if (state.exists) {
740
+ backupPath = await writeBackup(io, filePath, state.raw);
741
+ }
742
+ await atomicWrite(io, filePath, serialize(doc));
743
+ const after = await readDoc(io, filePath);
744
+ let verified = false;
745
+ if (after.doc !== null) {
746
+ const afterServers = after.doc["mcpServers"];
747
+ if (isPlainObject(afterServers)) {
748
+ verified = deepEqual(afterServers[SERVER_NAME], entry);
749
+ }
750
+ }
751
+ if (!verified) {
752
+ if (backupPath !== void 0) {
753
+ await restoreNewestBackup(io, filePath);
754
+ }
755
+ if (backupPath !== void 0) {
756
+ return { ok: false, changed: false, reason: "verify", backupPath };
757
+ }
758
+ return { ok: false, changed: false, reason: "verify" };
759
+ }
760
+ if (backupPath !== void 0) {
761
+ return { ok: true, changed: true, backupPath };
762
+ }
763
+ return { ok: true, changed: true };
764
+ }
765
+ async function removeServerKey(io, filePath) {
766
+ const state = await readDoc(io, filePath);
767
+ if (!state.exists) {
768
+ return { ok: true, changed: false, reason: "missing" };
769
+ }
770
+ if (state.parseError || state.doc === null) {
771
+ return { ok: false, changed: false, reason: "parse" };
772
+ }
773
+ const servers = state.doc["mcpServers"];
774
+ if (!isPlainObject(servers) || !(SERVER_NAME in servers)) {
775
+ return { ok: true, changed: false, reason: "missing" };
776
+ }
777
+ delete servers[SERVER_NAME];
778
+ const backupPath = await writeBackup(io, filePath, state.raw);
779
+ await atomicWrite(io, filePath, serialize(state.doc));
780
+ const after = await readDoc(io, filePath);
781
+ let verified = false;
782
+ if (after.doc !== null) {
783
+ const afterServers = after.doc["mcpServers"];
784
+ if (isPlainObject(afterServers)) {
785
+ verified = !(SERVER_NAME in afterServers);
786
+ } else if (afterServers === void 0) {
787
+ verified = true;
788
+ }
789
+ }
790
+ if (!verified) {
791
+ await restoreNewestBackup(io, filePath);
792
+ return { ok: false, changed: false, reason: "verify", backupPath };
793
+ }
794
+ return { ok: true, changed: true, backupPath };
795
+ }
796
+ async function detectHosts(io) {
797
+ const detected = [];
798
+ const useShell = io.platform === "win32";
799
+ const cliDetected = /* @__PURE__ */ new Set();
800
+ for (const host of CLI_HOSTS) {
801
+ let responded = false;
802
+ try {
803
+ const result = await io.spawn(host.bin, ["--version"], {
804
+ shell: useShell
805
+ });
806
+ responded = result.code === 0;
807
+ } catch {
808
+ responded = false;
809
+ }
810
+ if (responded) {
811
+ cliDetected.add(host.id);
812
+ detected.push({
813
+ tier: 1,
814
+ id: host.id,
815
+ displayName: host.displayName,
816
+ cli: host
817
+ });
818
+ }
819
+ }
820
+ for (const host of FILE_HOSTS) {
821
+ const configPath = host.configPath(io);
822
+ if (configPath === null) {
823
+ continue;
824
+ }
825
+ let present = await io.exists(configPath);
826
+ if (!present) {
827
+ const cut = Math.max(
828
+ configPath.lastIndexOf("/"),
829
+ configPath.lastIndexOf("\\")
830
+ );
831
+ if (cut > 0) {
832
+ present = await io.exists(configPath.slice(0, cut));
833
+ }
834
+ }
835
+ if (present) {
836
+ detected.push({
837
+ tier: 2,
838
+ id: host.id,
839
+ displayName: host.displayName,
840
+ file: host,
841
+ configPath
842
+ });
843
+ }
844
+ }
845
+ for (const host of SNIPPET_HOSTS) {
846
+ let present = false;
847
+ try {
848
+ present = await host.detect(io, { cliDetected });
849
+ } catch {
850
+ present = false;
851
+ }
852
+ if (present) {
853
+ detected.push({
854
+ tier: 3,
855
+ id: host.id,
856
+ displayName: host.displayName,
857
+ snippet: host
858
+ });
859
+ }
860
+ }
861
+ return detected;
862
+ }
863
+ function tierLabel(tier) {
864
+ if (tier === 1) {
865
+ return "\uACF5\uC2DD \uBA85\uB839\uC5B4\uB85C \uC5F0\uACB0\uD574\uC694";
866
+ }
867
+ if (tier === 2) {
868
+ return "\uC124\uC815 \uD30C\uC77C\uC5D0 \uC548\uC804\uD558\uAC8C \uCD94\uAC00\uD574\uC694";
869
+ }
870
+ return "\uC9C1\uC811 \uBD99\uC5EC\uB123\uB3C4\uB85D \uC548\uB0B4\uB9CC \uD574\uC694";
871
+ }
872
+ function printDetected(io, detected) {
873
+ io.out("\uC5F0\uACB0\uD560 \uC218 \uC788\uB294 \uD504\uB85C\uADF8\uB7A8\uC744 \uCC3E\uC558\uC5B4\uC694:");
874
+ for (const tier of [1, 2, 3]) {
875
+ const group = detected.filter((d) => d.tier === tier);
876
+ if (group.length === 0) {
877
+ continue;
878
+ }
879
+ io.out(` ${tierLabel(tier)}:`);
880
+ for (const d of group) {
881
+ io.out(` - ${d.displayName}`);
882
+ }
883
+ }
884
+ }
885
+ function printSupportedApps(io) {
886
+ io.out(NO_HOSTS_DETECTED);
887
+ io.out(SUPPORTED_APPS_HEADER);
888
+ for (const app of SUPPORTED_APPS) {
889
+ io.out(` - ${app.name}: ${app.url}`);
890
+ }
891
+ io.out(AFTER_INSTALL_RETRY);
892
+ }
893
+ async function offerCliInstall(io) {
894
+ if (!io.isInteractive()) {
895
+ return null;
896
+ }
897
+ io.out(NO_HOSTS_DETECTED);
898
+ io.out("");
899
+ io.out(CLI_OFFER_INTRO);
900
+ INSTALLABLE_CLIS.forEach((cli, i) => {
901
+ io.out(` ${i + 1}. ${cli.displayName}`);
902
+ });
903
+ io.out(` ${CLI_OFFER_SKIP_LABEL}`);
904
+ const answer = await io.prompt(
905
+ `${CLI_OFFER_QUESTION} (0-${INSTALLABLE_CLIS.length}):`
906
+ );
907
+ const n = Number(answer.trim());
908
+ if (!Number.isInteger(n) || n < 1 || n > INSTALLABLE_CLIS.length) {
909
+ return null;
910
+ }
911
+ return INSTALLABLE_CLIS[n - 1] ?? null;
912
+ }
913
+ async function installCli(io, cli) {
914
+ io.out("");
915
+ io.out(cliInstalling(cli.displayName, cli.npmPackage));
916
+ let code = -1;
917
+ try {
918
+ const result = await io.spawn("npm", ["install", "-g", cli.npmPackage], {
919
+ shell: io.platform === "win32",
920
+ inheritStdio: true
921
+ });
922
+ code = result.code;
923
+ } catch {
924
+ code = -1;
925
+ }
926
+ if (code === 0) {
927
+ io.out(cliInstalled(cli.displayName));
928
+ return true;
929
+ }
930
+ io.out(cliInstallFailed(cli.displayName));
931
+ return false;
932
+ }
933
+ function printResult(io, result) {
934
+ let label;
935
+ if (result.status === "success") {
936
+ label = "[\uC131\uACF5]";
937
+ } else if (result.status === "failed") {
938
+ label = "[\uC2E4\uD328]";
939
+ } else {
940
+ label = "[\uAC74\uB108\uB700]";
941
+ }
942
+ let line = `${label} ${result.displayName}`;
943
+ if (result.message !== void 0 && result.message !== "") {
944
+ line = `${line} \u2014 ${result.message}`;
945
+ }
946
+ io.out(line);
947
+ if (result.backupPath !== void 0) {
948
+ io.out(` \uBC31\uC5C5: ${result.backupPath}`);
949
+ }
950
+ }
951
+ function printSnippetBlock(io, host, opts) {
952
+ io.out("");
953
+ const path = host.detectedPath(io);
954
+ if (path !== null) {
955
+ io.out(`${host.displayName} \u2014 \uCC3E\uC740 \uC704\uCE58: ${path}`);
956
+ } else {
957
+ io.out(`${host.displayName} \uB97C \uCC3E\uC558\uC5B4\uC694.`);
958
+ }
959
+ io.out(host.reason);
960
+ io.out(host.pasteWhere);
961
+ io.out(host.buildSnippet(opts, io.platform));
962
+ }
963
+ function isPermissionError(error) {
964
+ if (typeof error !== "object" || error === null) {
965
+ return false;
966
+ }
967
+ const code = error.code;
968
+ if (code === "EACCES" || code === "EPERM") {
969
+ return true;
970
+ }
971
+ return false;
972
+ }
973
+ function describeError(error) {
974
+ if (isPermissionError(error)) {
975
+ return PERMISSION_DENIED_HINT;
976
+ }
977
+ if (error instanceof Error && error.message !== "") {
978
+ return `\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uBB38\uC81C\uAC00 \uC0DD\uACBC\uC5B4\uC694. (${error.message})`;
979
+ }
980
+ return "\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uBB38\uC81C\uAC00 \uC0DD\uACBC\uC5B4\uC694.";
981
+ }
982
+ function answeredNo(answer) {
983
+ const normalized = answer.trim().toLowerCase();
984
+ if (normalized === "n" || normalized === "no") {
985
+ return true;
986
+ }
987
+ return false;
988
+ }
989
+ function tier2Snippet(host, opts, platform) {
990
+ const entry = buildEntryForHost(host, opts, platform);
991
+ return JSON.stringify({ mcpServers: { [SERVER_NAME]: entry } }, null, 2);
992
+ }
993
+ async function applyCliInstall(io, host, opts) {
994
+ const useShell = io.platform === "win32";
995
+ await io.spawn(host.bin, host.buildRemoveArgs(), { shell: useShell });
996
+ const result = await io.spawn(host.bin, host.buildAddArgs(opts), {
997
+ shell: useShell
998
+ });
999
+ if (result.code === 0) {
1000
+ return {
1001
+ hostId: host.id,
1002
+ displayName: host.displayName,
1003
+ tier: 1,
1004
+ status: "success",
1005
+ message: "\uC5F0\uACB0\uD588\uC5B4\uC694."
1006
+ };
1007
+ }
1008
+ return {
1009
+ hostId: host.id,
1010
+ displayName: host.displayName,
1011
+ tier: 1,
1012
+ status: "failed",
1013
+ message: `\uBA85\uB839\uC5B4 \uC2E4\uD589\uC774 \uC2E4\uD328\uD588\uC5B4\uC694. (\uC885\uB8CC \uCF54\uB4DC ${result.code})`
1014
+ };
1015
+ }
1016
+ async function applyFileInstall(io, detected, opts) {
1017
+ const host = detected.file;
1018
+ const configPath = detected.configPath;
1019
+ if (host === void 0 || configPath === void 0) {
1020
+ return {
1021
+ hostId: detected.id,
1022
+ displayName: detected.displayName,
1023
+ tier: 2,
1024
+ status: "failed",
1025
+ message: describeError(null)
1026
+ };
1027
+ }
1028
+ const entry = buildEntryForHost(host, opts, io.platform);
1029
+ const outcome = await upsertServerKey(io, configPath, entry);
1030
+ if (outcome.ok) {
1031
+ const result = {
1032
+ hostId: host.id,
1033
+ displayName: host.displayName,
1034
+ tier: 2,
1035
+ status: "success",
1036
+ message: "\uC124\uC815 \uD30C\uC77C\uC5D0 \uCD94\uAC00\uD588\uC5B4\uC694."
1037
+ };
1038
+ if (outcome.backupPath !== void 0) {
1039
+ result.backupPath = outcome.backupPath;
1040
+ }
1041
+ return result;
1042
+ }
1043
+ if (outcome.reason === "parse") {
1044
+ io.out(
1045
+ `${host.displayName} \uC124\uC815 \uD30C\uC77C\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694: ${configPath}`
1046
+ );
1047
+ io.out("\uC544\uB798 \uB0B4\uC6A9\uC744 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.");
1048
+ io.out(tier2Snippet(host, opts, io.platform));
1049
+ return {
1050
+ hostId: host.id,
1051
+ displayName: host.displayName,
1052
+ tier: 2,
1053
+ status: "failed",
1054
+ message: "\uC124\uC815 \uD30C\uC77C\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC5B4\uC694. \uC704\uC5D0 \uCD9C\uB825\uB41C \uB0B4\uC6A9\uC744 \uC9C1\uC811 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694."
1055
+ };
1056
+ }
1057
+ const failed = {
1058
+ hostId: host.id,
1059
+ displayName: host.displayName,
1060
+ tier: 2,
1061
+ status: "failed",
1062
+ message: "\uC4F0\uAE30 \uAC80\uC99D\uC5D0 \uC2E4\uD328\uD574\uC11C \uBC31\uC5C5\uC73C\uB85C \uB418\uB3CC\uB838\uC5B4\uC694. \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694."
1063
+ };
1064
+ if (outcome.backupPath !== void 0) {
1065
+ failed.backupPath = outcome.backupPath;
1066
+ }
1067
+ return failed;
1068
+ }
1069
+ async function applyCliUninstall(io, host) {
1070
+ const useShell = io.platform === "win32";
1071
+ const result = await io.spawn(host.bin, host.buildRemoveArgs(), {
1072
+ shell: useShell
1073
+ });
1074
+ if (result.code === 0) {
1075
+ return {
1076
+ hostId: host.id,
1077
+ displayName: host.displayName,
1078
+ tier: 1,
1079
+ status: "success",
1080
+ message: "\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC5B4\uC694."
1081
+ };
1082
+ }
1083
+ return {
1084
+ hostId: host.id,
1085
+ displayName: host.displayName,
1086
+ tier: 1,
1087
+ status: "skipped",
1088
+ message: "\uC774\uBBF8 \uD574\uC81C\uB418\uC5B4 \uC788\uAC70\uB098 \uB4F1\uB85D\uB41C \uD56D\uBAA9\uC774 \uC5C6\uC5B4\uC694."
1089
+ };
1090
+ }
1091
+ async function applyFileUninstall(io, detected) {
1092
+ const host = detected.file;
1093
+ const configPath = detected.configPath;
1094
+ if (host === void 0 || configPath === void 0) {
1095
+ return {
1096
+ hostId: detected.id,
1097
+ displayName: detected.displayName,
1098
+ tier: 2,
1099
+ status: "failed",
1100
+ message: describeError(null)
1101
+ };
1102
+ }
1103
+ const outcome = await removeServerKey(io, configPath);
1104
+ if (outcome.ok && outcome.changed) {
1105
+ const result = {
1106
+ hostId: host.id,
1107
+ displayName: host.displayName,
1108
+ tier: 2,
1109
+ status: "success",
1110
+ message: "\uC124\uC815 \uD30C\uC77C\uC5D0\uC11C \uD56D\uBAA9\uC744 \uC9C0\uC6E0\uC5B4\uC694."
1111
+ };
1112
+ if (outcome.backupPath !== void 0) {
1113
+ result.backupPath = outcome.backupPath;
1114
+ }
1115
+ return result;
1116
+ }
1117
+ if (outcome.ok) {
1118
+ return {
1119
+ hostId: host.id,
1120
+ displayName: host.displayName,
1121
+ tier: 2,
1122
+ status: "skipped",
1123
+ message: "\uC124\uC815\uC5D0 \uC9C0\uC6B8 \uD56D\uBAA9\uC774 \uC5C6\uC5B4\uC694."
1124
+ };
1125
+ }
1126
+ if (outcome.reason === "parse") {
1127
+ return {
1128
+ hostId: host.id,
1129
+ displayName: host.displayName,
1130
+ tier: 2,
1131
+ status: "failed",
1132
+ message: `\uC124\uC815 \uD30C\uC77C\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC5B4\uC694. "${SERVER_NAME}" \uD56D\uBAA9\uC774 \uC788\uB2E4\uBA74 \uC9C1\uC811 \uC9C0\uC6CC \uC8FC\uC138\uC694: ${configPath}`
1133
+ };
1134
+ }
1135
+ const failed = {
1136
+ hostId: host.id,
1137
+ displayName: host.displayName,
1138
+ tier: 2,
1139
+ status: "failed",
1140
+ message: "\uC4F0\uAE30 \uAC80\uC99D\uC5D0 \uC2E4\uD328\uD574\uC11C \uBC31\uC5C5\uC73C\uB85C \uB418\uB3CC\uB838\uC5B4\uC694."
1141
+ };
1142
+ if (outcome.backupPath !== void 0) {
1143
+ failed.backupPath = outcome.backupPath;
1144
+ }
1145
+ return failed;
1146
+ }
1147
+ function filterByRequestedHosts(detected, hosts) {
1148
+ if (hosts === void 0 || hosts.length === 0) {
1149
+ return detected;
1150
+ }
1151
+ return detected.filter((d) => hosts.includes(d.id));
1152
+ }
1153
+ async function resolveInstallCredentials(opts, io) {
1154
+ if (typeof opts.token === "string" && opts.token !== "") {
1155
+ return opts;
1156
+ }
1157
+ if (!io.isInteractive()) {
1158
+ io.out(TOKEN_REQUIRED_NON_INTERACTIVE);
1159
+ return null;
1160
+ }
1161
+ io.out(TOKEN_PROMPT_GUIDE);
1162
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1163
+ const entered = await io.prompt(TOKEN_PROMPT_QUESTION);
1164
+ if (TOKEN_RE.test(entered)) {
1165
+ return {
1166
+ ...opts,
1167
+ token: entered,
1168
+ extensionId: opts.extensionId ?? DEFAULT_EXTENSION_ID
1169
+ };
1170
+ }
1171
+ if (attempt === 0) io.out(TOKEN_PROMPT_RETRY);
1172
+ }
1173
+ return { ...opts, extensionId: opts.extensionId ?? DEFAULT_EXTENSION_ID };
1174
+ }
1175
+ async function runInstall(opts, io) {
1176
+ printBanner((line) => io.out(line), INSTALL_SUBTITLE);
1177
+ const resolved = await resolveInstallCredentials(opts, io);
1178
+ if (resolved === null) return 1;
1179
+ const validationError = validateInstallOptions(resolved);
1180
+ if (validationError !== null) {
1181
+ io.out(validationError);
1182
+ return 1;
1183
+ }
1184
+ const entryOpts = {
1185
+ version: resolved.version,
1186
+ token: resolved.token,
1187
+ extensionId: resolved.extensionId
1188
+ };
1189
+ if (resolved.port !== void 0) {
1190
+ entryOpts.port = resolved.port;
1191
+ }
1192
+ let detected = filterByRequestedHosts(await detectHosts(io), opts.hosts);
1193
+ if (detected.length === 0) {
1194
+ const chosen = await offerCliInstall(io);
1195
+ if (chosen !== null && await installCli(io, chosen)) {
1196
+ detected = filterByRequestedHosts(await detectHosts(io), opts.hosts);
1197
+ if (detected.length === 0) {
1198
+ io.out(cliInstalledRetry(chosen.displayName));
1199
+ return 0;
1200
+ }
1201
+ }
1202
+ if (detected.length === 0) {
1203
+ printSupportedApps(io);
1204
+ return 1;
1205
+ }
1206
+ }
1207
+ printDetected(io, detected);
1208
+ const actionable = detected.filter((d) => d.tier !== 3);
1209
+ if (actionable.length > 0 && opts.yes !== true) {
1210
+ const answer = await io.ask(installQuestion(actionable.length));
1211
+ if (answeredNo(answer)) {
1212
+ io.out(NOTHING_CHANGED);
1213
+ return 0;
1214
+ }
1215
+ }
1216
+ const results = [];
1217
+ for (const d of detected) {
1218
+ try {
1219
+ if (d.tier === 1 && d.cli !== void 0) {
1220
+ results.push(await applyCliInstall(io, d.cli, entryOpts));
1221
+ } else if (d.tier === 2) {
1222
+ results.push(await applyFileInstall(io, d, entryOpts));
1223
+ } else if (d.snippet !== void 0) {
1224
+ results.push({
1225
+ hostId: d.id,
1226
+ displayName: d.displayName,
1227
+ tier: 3,
1228
+ status: "skipped",
1229
+ message: `${d.snippet.reason} \uC544\uB798 \uC548\uB0B4\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694.`
1230
+ });
1231
+ }
1232
+ } catch (error) {
1233
+ results.push({
1234
+ hostId: d.id,
1235
+ displayName: d.displayName,
1236
+ tier: d.tier,
1237
+ status: "failed",
1238
+ message: describeError(error)
1239
+ });
1240
+ }
1241
+ }
1242
+ io.out("");
1243
+ for (const result of results) {
1244
+ printResult(io, result);
1245
+ if (result.status === "success" && result.tier === 1) {
1246
+ const cliHost = CLI_HOSTS.find((h) => h.id === result.hostId);
1247
+ if (cliHost !== void 0 && cliHost.note !== void 0) {
1248
+ io.out(` ${cliHost.note}`);
1249
+ }
1250
+ }
1251
+ }
1252
+ for (const d of detected) {
1253
+ if (d.tier === 3 && d.snippet !== void 0) {
1254
+ printSnippetBlock(io, d.snippet, entryOpts);
1255
+ }
1256
+ }
1257
+ const anyConfigured = results.some((r) => r.status === "success");
1258
+ const anyFailed = results.some((r) => r.status === "failed" && r.tier !== 3);
1259
+ if (anyConfigured) {
1260
+ io.out("");
1261
+ io.out(RESTART_NOTICE);
1262
+ }
1263
+ if (anyFailed) {
1264
+ return 1;
1265
+ }
1266
+ return 0;
1267
+ }
1268
+ async function runUninstall(opts, io) {
1269
+ printBanner((line) => io.out(line), UNINSTALL_SUBTITLE);
1270
+ const validationError = validateUninstallOptions(opts);
1271
+ if (validationError !== null) {
1272
+ io.out(validationError);
1273
+ return 1;
1274
+ }
1275
+ const detected = filterByRequestedHosts(await detectHosts(io), opts.hosts);
1276
+ if (detected.length === 0) {
1277
+ io.out("\uC815\uB9AC\uD560 \uD56D\uBAA9\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC5B4\uC694.");
1278
+ io.out(UNINSTALL_DONE);
1279
+ io.out(UNINSTALL_TOKEN_REMINDER);
1280
+ return 0;
1281
+ }
1282
+ printDetected(io, detected);
1283
+ const actionable = detected.filter((d) => d.tier !== 3);
1284
+ if (actionable.length > 0 && opts.yes !== true) {
1285
+ const answer = await io.ask(uninstallQuestion(actionable.length));
1286
+ if (answeredNo(answer)) {
1287
+ io.out(NOTHING_CHANGED);
1288
+ return 0;
1289
+ }
1290
+ }
1291
+ const results = [];
1292
+ for (const d of detected) {
1293
+ try {
1294
+ if (d.tier === 1 && d.cli !== void 0) {
1295
+ results.push(await applyCliUninstall(io, d.cli));
1296
+ } else if (d.tier === 2) {
1297
+ results.push(await applyFileUninstall(io, d));
1298
+ } else if (d.snippet !== void 0) {
1299
+ results.push({
1300
+ hostId: d.id,
1301
+ displayName: d.displayName,
1302
+ tier: 3,
1303
+ status: "skipped",
1304
+ message: `\uC790\uB3D9 \uC218\uC815\uD558\uC9C0 \uC54A\uC544\uC694. \uC124\uC815\uC5D0 "${SERVER_NAME}" \uD56D\uBAA9\uC774 \uC788\uB2E4\uBA74 \uC9C1\uC811 \uC9C0\uC6CC \uC8FC\uC138\uC694. \uC9C0\uC6B0\uC9C0 \uC54A\uC544\uB3C4 \uC544\uBB34 \uC77C\uB3C4 \uD558\uC9C0 \uC54A\uC544\uC694.`
1305
+ });
1306
+ }
1307
+ } catch (error) {
1308
+ results.push({
1309
+ hostId: d.id,
1310
+ displayName: d.displayName,
1311
+ tier: d.tier,
1312
+ status: "failed",
1313
+ message: describeError(error)
1314
+ });
1315
+ }
1316
+ }
1317
+ io.out("");
1318
+ for (const result of results) {
1319
+ printResult(io, result);
1320
+ }
1321
+ io.out("");
1322
+ io.out(UNINSTALL_DONE);
1323
+ io.out(UNINSTALL_TOKEN_REMINDER);
1324
+ const anyFailed = results.some((r) => r.status === "failed" && r.tier !== 3);
1325
+ if (anyFailed) {
1326
+ return 1;
1327
+ }
1328
+ return 0;
1329
+ }
1330
+ function createNodeIo() {
1331
+ return {
1332
+ platform: process.platform,
1333
+ homedir() {
1334
+ return homedir();
1335
+ },
1336
+ env: process.env,
1337
+ async readFile(path) {
1338
+ return fs.readFile(path, "utf8");
1339
+ },
1340
+ async writeFile(path, content) {
1341
+ await fs.writeFile(path, content, "utf8");
1342
+ },
1343
+ async rename(from, to) {
1344
+ await fs.rename(from, to);
1345
+ },
1346
+ async mkdir(path) {
1347
+ await fs.mkdir(path, { recursive: true });
1348
+ },
1349
+ async exists(path) {
1350
+ try {
1351
+ await fs.access(path);
1352
+ return true;
1353
+ } catch {
1354
+ return false;
1355
+ }
1356
+ },
1357
+ async unlink(path) {
1358
+ await fs.unlink(path);
1359
+ },
1360
+ async listBackups(dir) {
1361
+ try {
1362
+ return await fs.readdir(dir);
1363
+ } catch {
1364
+ return [];
1365
+ }
1366
+ },
1367
+ async spawn(command, args, opts) {
1368
+ return new Promise((resolve) => {
1369
+ const useShell = opts.shell === true;
1370
+ const spawnCommand = useShell ? [command, ...args].join(" ") : command;
1371
+ const spawnArgs = useShell ? [] : args;
1372
+ const child = nodeSpawn(spawnCommand, spawnArgs, {
1373
+ shell: opts.shell,
1374
+ stdio: opts.inheritStdio ? ["inherit", "inherit", "inherit"] : ["ignore", "ignore", "ignore"]
1375
+ });
1376
+ child.on("error", () => {
1377
+ resolve({ code: -1 });
1378
+ });
1379
+ child.on("close", (code) => {
1380
+ if (code === null) {
1381
+ resolve({ code: -1 });
1382
+ } else {
1383
+ resolve({ code });
1384
+ }
1385
+ });
1386
+ });
1387
+ },
1388
+ async ask(question) {
1389
+ const rl = createInterface({
1390
+ input: process.stdin,
1391
+ output: process.stdout
1392
+ });
1393
+ try {
1394
+ return await new Promise((resolve) => {
1395
+ rl.question(`${question} (Y/n) `, resolve);
1396
+ });
1397
+ } finally {
1398
+ rl.close();
1399
+ }
1400
+ },
1401
+ async prompt(question) {
1402
+ const rl = createInterface({
1403
+ input: process.stdin,
1404
+ output: process.stdout
1405
+ });
1406
+ try {
1407
+ const answer = await new Promise((resolve) => {
1408
+ rl.question(`${question} `, resolve);
1409
+ });
1410
+ return answer.trim();
1411
+ } finally {
1412
+ rl.close();
1413
+ }
1414
+ },
1415
+ isInteractive() {
1416
+ return Boolean(process.stdin.isTTY);
1417
+ },
1418
+ out(line) {
1419
+ process.stdout.write(`${line}
1420
+ `);
1421
+ },
1422
+ now() {
1423
+ return /* @__PURE__ */ new Date();
1424
+ }
1425
+ };
1426
+ }
1427
+ var PACKAGE_NAME = "@modootoday/extension-app-mcp-installer";
1428
+ export {
1429
+ BACKUP_KEEP,
1430
+ CLI_HOSTS,
1431
+ CODEX_CHATGPT_NOTE,
1432
+ DEFAULT_PORT,
1433
+ EXTENSION_ID_RE,
1434
+ FILE_HOSTS,
1435
+ INSTALLABLE_CLIS,
1436
+ INSTALL_SUBTITLE,
1437
+ INVALID_EXTENSION_ID_MESSAGE,
1438
+ INVALID_PORT_MESSAGE,
1439
+ INVALID_TOKEN_MESSAGE,
1440
+ INVALID_VERSION_MESSAGE,
1441
+ NOTHING_CHANGED,
1442
+ NO_HOSTS_DETECTED,
1443
+ PACKAGE_NAME,
1444
+ PERMISSION_DENIED_HINT,
1445
+ PORT_RE,
1446
+ RESTART_NOTICE,
1447
+ SERVER_NAME,
1448
+ SERVER_PACKAGE,
1449
+ SNIPPET_HOSTS,
1450
+ SUPPORTED_APPS,
1451
+ TOKEN_RE,
1452
+ UNINSTALL_DONE,
1453
+ UNINSTALL_SUBTITLE,
1454
+ UNINSTALL_TOKEN_REMINDER,
1455
+ VERSION_RE,
1456
+ buildEnv,
1457
+ buildFileEntry,
1458
+ createNodeIo,
1459
+ detectHosts,
1460
+ formatBackupTimestamp,
1461
+ installQuestion,
1462
+ packageSpec,
1463
+ printBanner,
1464
+ removeServerKey,
1465
+ runInstall,
1466
+ runUninstall,
1467
+ uninstallQuestion,
1468
+ upsertServerKey,
1469
+ validateInstallOptions,
1470
+ validateUninstallOptions
1471
+ };