@runtypelabs/cli 2.59.1 → 2.59.2

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.
Files changed (84) hide show
  1. package/dist/chunk-0-T9NWjH.js +3899 -0
  2. package/dist/chunk-0x7UKC1U.js +17 -0
  3. package/dist/chunk-2ySxXtcg.js +418 -0
  4. package/dist/chunk-3An_BRHv.js +501 -0
  5. package/dist/chunk-6uTxgdiY.js +16218 -0
  6. package/dist/chunk-87nRnNRG.js +1363 -0
  7. package/dist/chunk-B3wo8uRx.js +322 -0
  8. package/dist/chunk-BGp3ZdBy.js +560 -0
  9. package/dist/chunk-BJQv9Elp.js +107 -0
  10. package/dist/chunk-BOpO6xE-.js +6053 -0
  11. package/dist/chunk-BQXXCL8B.js +102 -0
  12. package/dist/chunk-BV-8isOd.js +34 -0
  13. package/dist/chunk-BVMhjGVe.js +826 -0
  14. package/dist/chunk-BW2fg46T.js +141 -0
  15. package/dist/chunk-BWJdY5Ag.js +36 -0
  16. package/dist/chunk-BYJOrLhh.js +302 -0
  17. package/dist/chunk-BYab8EVL.js +127 -0
  18. package/dist/chunk-BfEKvp7h.js +82 -0
  19. package/dist/chunk-BhgLSfH6.js +1123 -0
  20. package/dist/chunk-BirUxTtH.js +296 -0
  21. package/dist/chunk-BmB6UVZ9.js +1559 -0
  22. package/dist/chunk-BnWhIqW1.js +109 -0
  23. package/dist/chunk-Bpfnfk0t.js +123 -0
  24. package/dist/chunk-BrhgdnM3.js +108 -0
  25. package/dist/chunk-Bt0XAgX5.js +327 -0
  26. package/dist/chunk-Bu0FJr6S.js +84 -0
  27. package/dist/chunk-BvEoKpN3.js +255 -0
  28. package/dist/chunk-BxNJgYbE.js +28 -0
  29. package/dist/chunk-C1OFhd7F.js +6131 -0
  30. package/dist/chunk-C7e7WwbU.js +331 -0
  31. package/dist/chunk-CDTYP6bN.js +558 -0
  32. package/dist/chunk-CGDQoqDC.js +24 -0
  33. package/dist/chunk-CJraEBVd.js +127 -0
  34. package/dist/chunk-CQrNhOtv.js +40 -0
  35. package/dist/chunk-CW6AzBH6.js +128 -0
  36. package/dist/chunk-CXgL4w-9.js +317 -0
  37. package/dist/chunk-CZrqoPyf.js +233 -0
  38. package/dist/chunk-CcFCyqbB.js +83 -0
  39. package/dist/chunk-CdaIvO-y.js +485 -0
  40. package/dist/chunk-CehSXtgK.js +624 -0
  41. package/dist/chunk-Ciqkop65.js +218 -0
  42. package/dist/chunk-CoAWdSiE.js +43 -0
  43. package/dist/chunk-Crb4y7t4.js +830 -0
  44. package/dist/chunk-CrlqIfmb.js +337 -0
  45. package/dist/chunk-Cs4pwCQ-.js +324 -0
  46. package/dist/chunk-CwM_9y01.js +519 -0
  47. package/dist/chunk-CyRbIJg8.js +440 -0
  48. package/dist/chunk-D0OHsyMy.js +212 -0
  49. package/dist/chunk-D3y2bURs.js +1347 -0
  50. package/dist/chunk-D4_mJk03.js +2338 -0
  51. package/dist/chunk-D6C8rned.js +578 -0
  52. package/dist/chunk-D9ZbUnks.js +11 -0
  53. package/dist/chunk-DEPmLshX.js +834 -0
  54. package/dist/chunk-DFj1UPB2.js +330 -0
  55. package/dist/chunk-DGdFdZVf.js +197 -0
  56. package/dist/chunk-DGv7X9S_.js +63 -0
  57. package/dist/chunk-DJQ47WYZ.js +664 -0
  58. package/dist/chunk-DJw3O61j.js +1974 -0
  59. package/dist/chunk-DK4FbWVK.js +100 -0
  60. package/dist/chunk-DSRYhUus.js +993 -0
  61. package/dist/chunk-DajZ9Eg4.js +299 -0
  62. package/dist/chunk-Df4dunvU.js +195 -0
  63. package/dist/chunk-DjNTPhfO.js +481 -0
  64. package/dist/chunk-Dn8eaJuu.js +1042 -0
  65. package/dist/chunk-DwDWqSxM.js +637 -0
  66. package/dist/chunk-Dz7cEtxl.js +84 -0
  67. package/dist/chunk-E_0bgjrS.js +359 -0
  68. package/dist/chunk-KPYN2vUp.js +776 -0
  69. package/dist/chunk-KcqPQJBi.js +86 -0
  70. package/dist/chunk-N9H0JnoG.js +349 -0
  71. package/dist/chunk-NONFcXkn.js +728 -0
  72. package/dist/chunk-PrFjcpmH.js +256 -0
  73. package/dist/chunk-QSfNYiw9.js +11 -0
  74. package/dist/chunk-R265bzj9.js +226 -0
  75. package/dist/chunk-X9V_r6jM.js +166 -0
  76. package/dist/chunk-ZBtPUtyA.js +55 -0
  77. package/dist/chunk-ZCTfmOs7.js +4939 -0
  78. package/dist/chunk-h6oavj0t.js +97 -0
  79. package/dist/chunk-i7DwkvvD.js +42 -0
  80. package/dist/chunk-mG-4eZ6S.js +203 -0
  81. package/dist/chunk-o0ygUbCf.js +313 -0
  82. package/dist/chunk-ucu5lrDZ.js +5715 -0
  83. package/dist/index.js +11367 -73789
  84. package/package.json +4 -4
@@ -0,0 +1,664 @@
1
+ #!/usr/bin/env node
2
+ import { D as runSkillsInstall, t as isTTY } from "./index.js";
3
+ import { a as printJson } from "./chunk-CcFCyqbB.js";
4
+ import { t as promptSelect } from "./chunk-BrhgdnM3.js";
5
+ import { Command } from "commander";
6
+ import chalk from "chalk";
7
+ import { copyFileSync, existsSync, realpathSync, unlinkSync } from "fs";
8
+ import { delimiter, join } from "path";
9
+ import { spawn } from "child_process";
10
+ import { tmpdir } from "os";
11
+ // src/lib/mcp-install.ts
12
+ const RUNTYPE_MCP_NAME = "runtype";
13
+ const RUNTYPE_MCP_URL = "https://api.runtype.com/v1/mcp/protocol";
14
+ const SUPPORTED_MCP_AGENTS = [
15
+ "claude-code",
16
+ "codex",
17
+ "cursor",
18
+ "gemini-cli",
19
+ "opencode",
20
+ "vscode"
21
+ ];
22
+ function resolveVscodeCli(options = {}) {
23
+ const platform = options.platform ?? process.platform;
24
+ const pathValue = options.pathValue ?? process.env.PATH;
25
+ const pathExists = options.exists ?? existsSync;
26
+ const resolveRealpath = options.realpath ?? realpathSync;
27
+ const macosCli = "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code";
28
+ if (platform === "darwin" && pathExists(macosCli)) return { executable: macosCli };
29
+ if (platform !== "win32") {
30
+ const codePath = pathValue?.split(delimiter).map((directory) => join(directory, "code")).find((candidate) => pathExists(candidate));
31
+ if (codePath) {
32
+ const resolvedPath = resolveRealpath(codePath);
33
+ if (/(?:^|[\\/])cursor(?:\.app)?(?:[\\/]|$)/i.test(resolvedPath)) return { error: "The `code` command on PATH belongs to Cursor, not VS Code. Install the VS Code shell command and retry." };
34
+ }
35
+ }
36
+ return { executable: "code" };
37
+ }
38
+ const vscodeCli = resolveVscodeCli();
39
+ const MCP_AGENT_DEFINITIONS = {
40
+ "claude-code": {
41
+ displayName: "Claude Code",
42
+ executable: "claude",
43
+ inspectArgs: [
44
+ "mcp",
45
+ "get",
46
+ RUNTYPE_MCP_NAME
47
+ ],
48
+ addArgs: [
49
+ "mcp",
50
+ "add",
51
+ "--transport",
52
+ "http",
53
+ "--scope",
54
+ "user",
55
+ RUNTYPE_MCP_NAME,
56
+ RUNTYPE_MCP_URL
57
+ ],
58
+ forceFallbackWriter: true,
59
+ authExecutable: "claude",
60
+ authArgs: [
61
+ "mcp",
62
+ "login",
63
+ RUNTYPE_MCP_NAME
64
+ ],
65
+ restartInstruction: "Start a new Claude Code session (or reload MCP servers).",
66
+ skillsAgentId: "claude"
67
+ },
68
+ codex: {
69
+ displayName: "Codex",
70
+ executable: "codex",
71
+ inspectArgs: [
72
+ "mcp",
73
+ "get",
74
+ RUNTYPE_MCP_NAME,
75
+ "--json"
76
+ ],
77
+ addArgs: [
78
+ "mcp",
79
+ "add",
80
+ RUNTYPE_MCP_NAME,
81
+ "--url",
82
+ RUNTYPE_MCP_URL
83
+ ],
84
+ forceFallbackWriter: true,
85
+ authExecutable: "codex",
86
+ authArgs: [
87
+ "mcp",
88
+ "login",
89
+ RUNTYPE_MCP_NAME
90
+ ],
91
+ restartInstruction: "Restart Codex or start a new Codex task.",
92
+ skillsAgentId: "codex"
93
+ },
94
+ cursor: {
95
+ displayName: "Cursor",
96
+ effectiveInspectExecutable: "cursor-agent",
97
+ effectiveInspectArgs: ["mcp", "list"],
98
+ authExecutable: "cursor-agent",
99
+ authArgs: [
100
+ "mcp",
101
+ "login",
102
+ RUNTYPE_MCP_NAME
103
+ ],
104
+ fallbackWriter: true,
105
+ restartInstruction: "Restart Cursor or start a new Cursor agent session.",
106
+ skillsAgentId: "cursor"
107
+ },
108
+ "gemini-cli": {
109
+ displayName: "Gemini CLI",
110
+ executable: "gemini",
111
+ inspectArgs: ["mcp", "list"],
112
+ addArgs: [
113
+ "mcp",
114
+ "add",
115
+ "--scope",
116
+ "user",
117
+ "--transport",
118
+ "http",
119
+ RUNTYPE_MCP_NAME,
120
+ RUNTYPE_MCP_URL
121
+ ],
122
+ forceFallbackWriter: true,
123
+ restartInstruction: "Run `/mcp reload` or start a new Gemini CLI session.",
124
+ manualAuthInstruction: "In Gemini CLI, run `/mcp auth runtype`, then `/mcp reload`.",
125
+ skillsAgentId: "gemini-cli"
126
+ },
127
+ opencode: {
128
+ displayName: "OpenCode",
129
+ executable: "opencode",
130
+ inspectArgs: ["mcp", "list"],
131
+ addArgs: [
132
+ "mcp",
133
+ "add",
134
+ RUNTYPE_MCP_NAME,
135
+ "--url",
136
+ RUNTYPE_MCP_URL
137
+ ],
138
+ forceFallbackWriter: true,
139
+ authExecutable: "opencode",
140
+ authArgs: [
141
+ "mcp",
142
+ "auth",
143
+ RUNTYPE_MCP_NAME
144
+ ],
145
+ restartInstruction: "Restart OpenCode or start a new OpenCode session.",
146
+ skillsAgentId: "opencode"
147
+ },
148
+ vscode: {
149
+ displayName: "VS Code",
150
+ executable: vscodeCli.executable,
151
+ executableError: vscodeCli.error,
152
+ addArgs: ["--add-mcp", JSON.stringify({
153
+ name: RUNTYPE_MCP_NAME,
154
+ type: "http",
155
+ url: RUNTYPE_MCP_URL
156
+ })],
157
+ restartInstruction: "Reload the VS Code window, then start a new chat.",
158
+ manualAuthInstruction: "Approve the browser OAuth prompt when Runtype is first used.",
159
+ skillsAgentId: "copilot"
160
+ }
161
+ };
162
+ function envFlag(value) {
163
+ return value !== void 0 && value !== "" && value !== "false" && value !== "0";
164
+ }
165
+ /**
166
+ * Prefer a strong signal from the running harness over filesystem detection.
167
+ * A user may have several agent config directories even though only one agent
168
+ * can pick up the new MCP connection after this command returns.
169
+ */
170
+ function detectActiveMcpAgent(env = process.env) {
171
+ if (envFlag(env.CODEX_SHELL) || envFlag(env.CODEX_THREAD_ID) || env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE?.toLowerCase().includes("codex")) return "codex";
172
+ if (envFlag(env.CLAUDECODE) || envFlag(env.CLAUDE_CODE_SESSION_ID) || envFlag(env.CLAUDE_CODE_ENTRYPOINT)) return "claude-code";
173
+ if (envFlag(env.CURSOR_AGENT) || envFlag(env.CURSOR_TRACE_ID)) return "cursor";
174
+ if (envFlag(env.GEMINI_CLI) || envFlag(env.GEMINI_CLI_NO_RELAUNCH)) return "gemini-cli";
175
+ if (envFlag(env.OPENCODE) || envFlag(env.OPENCODE_CLIENT)) return "opencode";
176
+ if (env.TERM_PROGRAM?.toLowerCase() === "vscode" && envFlag(env.VSCODE_PID)) return "vscode";
177
+ }
178
+ const AGENT_ALIASES = {
179
+ claude: "claude-code",
180
+ "claude-code": "claude-code",
181
+ codex: "codex",
182
+ cursor: "cursor",
183
+ gemini: "gemini-cli",
184
+ "gemini-cli": "gemini-cli",
185
+ opencode: "opencode",
186
+ "open-code": "opencode",
187
+ vscode: "vscode",
188
+ "vs-code": "vscode"
189
+ };
190
+ function normalizeMcpAgent(value) {
191
+ return AGENT_ALIASES[value.trim().toLowerCase()];
192
+ }
193
+ async function loadAddMcpAdapter() {
194
+ const addMcp = await import("add-mcp");
195
+ return {
196
+ detectGlobalAgents: async () => addMcp.detectGlobalAgents(),
197
+ inspect: async (agent) => {
198
+ const [result] = await addMcp.listInstalledServers({
199
+ global: true,
200
+ agents: [agent]
201
+ });
202
+ if (!result) throw new Error(`add-mcp did not return a global ${MCP_AGENT_DEFINITIONS[agent].displayName} configuration path.`);
203
+ return {
204
+ configPath: result.configPath,
205
+ server: result.servers.find((server) => server.serverName === RUNTYPE_MCP_NAME)
206
+ };
207
+ },
208
+ upsert: (agent) => addMcp.upsertServer(agent, RUNTYPE_MCP_NAME, {
209
+ type: "http",
210
+ url: RUNTYPE_MCP_URL
211
+ })
212
+ };
213
+ }
214
+ async function detectInstalledMcpAgents(adapter) {
215
+ const detected = await adapter.detectGlobalAgents();
216
+ return SUPPORTED_MCP_AGENTS.filter((agent) => detected.includes(agent));
217
+ }
218
+ const runCommand = async (executable, args, options = {}) => {
219
+ return new Promise((resolve) => {
220
+ const interactive = options.interactive === true;
221
+ const child = spawn(executable, args, {
222
+ stdio: interactive ? "inherit" : [
223
+ "ignore",
224
+ "pipe",
225
+ "pipe"
226
+ ],
227
+ env: process.env,
228
+ cwd: options.cwd
229
+ });
230
+ let stdout = "";
231
+ let stderr = "";
232
+ if (!interactive) {
233
+ child.stdout?.on("data", (chunk) => {
234
+ stdout += chunk.toString();
235
+ });
236
+ child.stderr?.on("data", (chunk) => {
237
+ stderr += chunk.toString();
238
+ });
239
+ }
240
+ child.on("error", (error) => {
241
+ resolve({
242
+ exitCode: null,
243
+ stdout,
244
+ stderr,
245
+ error: error.message
246
+ });
247
+ });
248
+ child.on("close", (exitCode) => {
249
+ resolve({
250
+ exitCode,
251
+ stdout,
252
+ stderr
253
+ });
254
+ });
255
+ });
256
+ };
257
+ function stripAnsi(value) {
258
+ return value.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "");
259
+ }
260
+ function inspectNativeOutput(agent, result) {
261
+ const output = stripAnsi(`${result.stdout}\n${result.stderr}`);
262
+ if (agent === "codex" && result.exitCode === 0) try {
263
+ const parsed = JSON.parse(result.stdout);
264
+ if (parsed.transport?.url === "https://api.runtype.com/v1/mcp/protocol") return "matching";
265
+ if (typeof parsed.transport?.url === "string") return "conflict";
266
+ } catch {}
267
+ if (nativeOutputHasMatchingServer(agent, output)) return "matching";
268
+ if (result.exitCode !== 0 && /(?:not found|does not exist|no mcp server|unknown mcp server)/i.test(output)) return "missing";
269
+ if (nativeOutputMentionsServerName(output)) return "conflict";
270
+ return "missing";
271
+ }
272
+ function nativeOutputHasMatchingServer(agent, output) {
273
+ if (![
274
+ "cursor",
275
+ "gemini-cli",
276
+ "opencode"
277
+ ].includes(agent)) return output.includes(RUNTYPE_MCP_URL);
278
+ const lines = output.split(/\r?\n/);
279
+ for (let index = 0; index < lines.length; index += 1) {
280
+ const line = lines[index];
281
+ if (!nativeOutputMentionsServerName(line)) continue;
282
+ if (line.includes("https://api.runtype.com/v1/mcp/protocol")) return true;
283
+ const nextLine = lines[index + 1];
284
+ const urlIndex = nextLine?.indexOf("https://api.runtype.com/v1/mcp/protocol") ?? -1;
285
+ if (urlIndex >= 0) {
286
+ const prefix = nextLine.slice(0, urlIndex);
287
+ if (!/[a-z0-9_-]/i.test(prefix)) return true;
288
+ }
289
+ }
290
+ return false;
291
+ }
292
+ function nativeOutputMentionsServerName(output) {
293
+ const withoutUrls = output.replace(/https?:\/\/\S+/gi, "");
294
+ return /\bruntype\b/i.test(withoutUrls);
295
+ }
296
+ async function configureWithNativeCli(agent, options) {
297
+ const definition = MCP_AGENT_DEFINITIONS[agent];
298
+ if (!definition.executable || !definition.addArgs) return {
299
+ configured: false,
300
+ changed: false,
301
+ error: definition.executableError ?? "Native MCP command is unavailable."
302
+ };
303
+ if (options.dryRun) return {
304
+ configured: true,
305
+ changed: true
306
+ };
307
+ let existing = "missing";
308
+ if (definition.inspectArgs) {
309
+ const inspection = await options.runner(definition.executable, definition.inspectArgs, { cwd: tmpdir() });
310
+ if (inspection.error?.includes("ENOENT")) {
311
+ if (agent === "codex") {
312
+ const adapter = options.adapter ?? await loadAddMcpAdapter();
313
+ return configureWithFallbackWriter(agent, {
314
+ force: options.force,
315
+ adapter
316
+ });
317
+ }
318
+ return {
319
+ configured: false,
320
+ changed: false,
321
+ error: `${definition.displayName}'s CLI (${definition.executable}) is not on PATH.`
322
+ };
323
+ }
324
+ existing = inspectNativeOutput(agent, inspection);
325
+ }
326
+ if (existing === "matching") return {
327
+ configured: true,
328
+ changed: false
329
+ };
330
+ if (existing === "conflict" && !options.force) return {
331
+ configured: false,
332
+ changed: false,
333
+ error: `An MCP server named "${RUNTYPE_MCP_NAME}" already points somewhere else. Re-run with --force to replace it.`
334
+ };
335
+ if (existing === "conflict" && definition.forceFallbackWriter) return configureWithFallbackWriter(agent, {
336
+ force: true,
337
+ adapter: options.adapter ?? await loadAddMcpAdapter()
338
+ });
339
+ const installation = await options.runner(definition.executable, definition.addArgs, { cwd: tmpdir() });
340
+ if (installation.error?.includes("ENOENT")) {
341
+ if (agent === "codex") {
342
+ const adapter = options.adapter ?? await loadAddMcpAdapter();
343
+ return configureWithFallbackWriter(agent, {
344
+ force: options.force,
345
+ adapter
346
+ });
347
+ }
348
+ return {
349
+ configured: false,
350
+ changed: false,
351
+ error: `${definition.displayName}'s CLI (${definition.executable}) is not on PATH.`
352
+ };
353
+ }
354
+ if (installation.exitCode !== 0) return {
355
+ configured: false,
356
+ changed: false,
357
+ error: stripAnsi(installation.stderr || installation.stdout).trim() || `Could not configure ${definition.displayName}.`
358
+ };
359
+ return {
360
+ configured: true,
361
+ changed: true
362
+ };
363
+ }
364
+ async function configureWithFallbackWriter(agent, options) {
365
+ let inspected;
366
+ try {
367
+ inspected = await options.adapter.inspect(agent);
368
+ } catch (error) {
369
+ return {
370
+ configured: false,
371
+ changed: false,
372
+ error: `Could not inspect ${MCP_AGENT_DEFINITIONS[agent].displayName}'s MCP configuration: ${getErrorMessage(error)}`
373
+ };
374
+ }
375
+ if (inspected.server?.identity === "https://api.runtype.com/v1/mcp/protocol") return {
376
+ configured: true,
377
+ changed: false,
378
+ configPath: inspected.configPath
379
+ };
380
+ if (inspected.server && !options.force) return {
381
+ configured: false,
382
+ changed: false,
383
+ configPath: inspected.configPath,
384
+ error: `An MCP server named "${RUNTYPE_MCP_NAME}" already points somewhere else. Re-run with --force to replace it.`
385
+ };
386
+ if (options.dryRun) return {
387
+ configured: true,
388
+ changed: true,
389
+ configPath: inspected.configPath
390
+ };
391
+ const backupPath = existsSync(inspected.configPath) ? `${inspected.configPath}.runtype.bak` : void 0;
392
+ if (backupPath) copyFileSync(inspected.configPath, backupPath);
393
+ let installation;
394
+ try {
395
+ installation = options.adapter.upsert(agent);
396
+ } catch (error) {
397
+ restoreFallbackConfig(inspected.configPath, backupPath);
398
+ return {
399
+ configured: false,
400
+ changed: false,
401
+ configPath: inspected.configPath,
402
+ backupPath,
403
+ error: `Could not update ${MCP_AGENT_DEFINITIONS[agent].displayName}'s MCP configuration: ${getErrorMessage(error)}`
404
+ };
405
+ }
406
+ if (!installation.success) {
407
+ restoreFallbackConfig(inspected.configPath, backupPath);
408
+ return {
409
+ configured: false,
410
+ changed: false,
411
+ configPath: installation.path,
412
+ backupPath,
413
+ error: installation.error ?? `Could not configure ${MCP_AGENT_DEFINITIONS[agent].displayName}.`
414
+ };
415
+ }
416
+ let verified;
417
+ try {
418
+ verified = await options.adapter.inspect(agent);
419
+ } catch (error) {
420
+ restoreFallbackConfig(inspected.configPath, backupPath);
421
+ return {
422
+ configured: false,
423
+ changed: false,
424
+ configPath: installation.path,
425
+ backupPath,
426
+ error: `The MCP config could not be verified, so the previous config was restored: ${getErrorMessage(error)}`
427
+ };
428
+ }
429
+ if (verified.server?.identity !== "https://api.runtype.com/v1/mcp/protocol") {
430
+ restoreFallbackConfig(inspected.configPath, backupPath);
431
+ return {
432
+ configured: false,
433
+ changed: false,
434
+ configPath: installation.path,
435
+ backupPath,
436
+ error: "The MCP config could not be verified, so the previous config was restored."
437
+ };
438
+ }
439
+ return {
440
+ configured: true,
441
+ changed: true,
442
+ configPath: installation.path,
443
+ backupPath
444
+ };
445
+ }
446
+ function getErrorMessage(error) {
447
+ return error instanceof Error ? error.message : String(error);
448
+ }
449
+ function restoreFallbackConfig(configPath, backupPath) {
450
+ if (backupPath) copyFileSync(backupPath, configPath);
451
+ else if (existsSync(configPath)) unlinkSync(configPath);
452
+ }
453
+ async function configureMcpAgent(agent, options = {}) {
454
+ try {
455
+ if (MCP_AGENT_DEFINITIONS[agent].fallbackWriter) {
456
+ const adapter = options.adapter ?? await loadAddMcpAdapter();
457
+ return configureWithFallbackWriter(agent, {
458
+ ...options,
459
+ adapter
460
+ });
461
+ }
462
+ return configureWithNativeCli(agent, {
463
+ ...options,
464
+ runner: options.runner ?? runCommand
465
+ });
466
+ } catch (error) {
467
+ return {
468
+ configured: false,
469
+ changed: false,
470
+ error: `Could not configure ${MCP_AGENT_DEFINITIONS[agent].displayName}: ${getErrorMessage(error)}`
471
+ };
472
+ }
473
+ }
474
+ async function authenticateMcpAgent(agent, options = {}) {
475
+ const definition = MCP_AGENT_DEFINITIONS[agent];
476
+ if (options.dryRun || options.login === false) return {
477
+ authenticated: false,
478
+ attempted: false,
479
+ instruction: definition.manualAuthInstruction ?? `Run \`${definition.authExecutable} ${definition.authArgs?.join(" ")}\` to complete OAuth.`
480
+ };
481
+ const effectiveError = await getEffectiveConfigurationError(agent, options.runner ?? runCommand);
482
+ if (effectiveError) return {
483
+ authenticated: false,
484
+ attempted: false,
485
+ error: effectiveError
486
+ };
487
+ if (!definition.authExecutable || !definition.authArgs) return {
488
+ authenticated: false,
489
+ attempted: false,
490
+ instruction: definition.manualAuthInstruction
491
+ };
492
+ const result = await (options.runner ?? runCommand)(definition.authExecutable, definition.authArgs, { interactive: true });
493
+ if (result.error?.includes("ENOENT")) return {
494
+ authenticated: false,
495
+ attempted: false,
496
+ instruction: `Run \`${definition.authExecutable} ${definition.authArgs.join(" ")}\` when that CLI is available.`
497
+ };
498
+ if (result.exitCode !== 0) return {
499
+ authenticated: false,
500
+ attempted: true,
501
+ error: `OAuth did not complete for ${definition.displayName}.`,
502
+ instruction: `Retry with \`${definition.authExecutable} ${definition.authArgs.join(" ")}\`.`
503
+ };
504
+ return {
505
+ authenticated: true,
506
+ attempted: true
507
+ };
508
+ }
509
+ async function getEffectiveConfigurationError(agent, runner) {
510
+ const definition = MCP_AGENT_DEFINITIONS[agent];
511
+ const executable = definition.effectiveInspectExecutable ?? definition.executable;
512
+ const args = definition.effectiveInspectArgs ?? definition.inspectArgs;
513
+ if (!executable || !args) return void 0;
514
+ const inspection = await runner(executable, args);
515
+ if (inspection.error?.includes("ENOENT")) return void 0;
516
+ if (inspectNativeOutput(agent, inspection) === "matching") return void 0;
517
+ return `The current project resolves an MCP server named "${RUNTYPE_MCP_NAME}" somewhere other than ${RUNTYPE_MCP_URL}. Remove or rename the project/local entry before completing OAuth.`;
518
+ }
519
+ function getMcpRestartInstruction(agent) {
520
+ return MCP_AGENT_DEFINITIONS[agent].restartInstruction;
521
+ }
522
+ function getMcpSkillsAgentId(agent) {
523
+ return MCP_AGENT_DEFINITIONS[agent].skillsAgentId;
524
+ }
525
+ async function selectAgent(options, tty) {
526
+ if (options.agent) {
527
+ const explicit = normalizeMcpAgent(options.agent);
528
+ if (!explicit) throw new Error(`Unsupported agent "${options.agent}". Use claude, codex, cursor, gemini, opencode, or vscode.`);
529
+ return explicit;
530
+ }
531
+ const active = detectActiveMcpAgent();
532
+ if (active) return active;
533
+ const detected = await detectInstalledMcpAgents(await loadAddMcpAdapter());
534
+ if (detected.length === 1) return detected[0];
535
+ if (detected.length === 0) throw new Error("Could not detect the current coding agent. Re-run with --agent <claude|codex|cursor|gemini|opencode|vscode>.");
536
+ if (!tty || !process.stdin.isTTY || !process.stdout.isTTY || options.json) throw new Error(`Several coding agents were detected (${detected.join(", ")}). Re-run with --agent <id> so only the current harness is changed.`);
537
+ return promptSelect("Which coding agent should Runtype configure?", detected.map((agent) => ({
538
+ value: agent,
539
+ label: MCP_AGENT_DEFINITIONS[agent].displayName,
540
+ description: "user-level MCP configuration"
541
+ })), { promptLabel: "Agent" });
542
+ }
543
+ // src/commands/install-mcp.ts
544
+ async function installSkillsForAgent(agent, jsonMode) {
545
+ const rosieId = getMcpSkillsAgentId(agent);
546
+ try {
547
+ const result = await runSkillsInstall([{
548
+ name: MCP_AGENT_DEFINITIONS[agent].displayName,
549
+ rosieId
550
+ }], {
551
+ accepted: true,
552
+ quiet: jsonMode
553
+ });
554
+ if (!result) return {
555
+ status: "error",
556
+ installedAgents: [],
557
+ failedAgents: [rosieId],
558
+ error: "The Runtype skill installer did not complete."
559
+ };
560
+ return {
561
+ status: result.failedAgents.length > 0 ? "error" : "installed",
562
+ ...result
563
+ };
564
+ } catch (error) {
565
+ return {
566
+ status: "error",
567
+ installedAgents: [],
568
+ failedAgents: [rosieId],
569
+ error: error instanceof Error ? error.message : String(error)
570
+ };
571
+ }
572
+ }
573
+ const installMcpCommand = new Command("install-mcp").description("Install Runtype skills and the OAuth MCP server for this coding agent").option("--agent <id>", "Coding agent to configure (auto-detected by default)").option("--force", "Replace a conflicting MCP server named \"runtype\"").option("--dry-run", "Show the selected agent and planned changes without writing them").option("--no-login", "Configure MCP without starting browser OAuth").option("--no-skills", "Configure MCP without installing Runtype agent skills").option("--json", "Structured JSON output; implies --no-login").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options, cmd) => {
574
+ const globals = cmd.optsWithGlobals();
575
+ const jsonMode = !!(options.json ?? globals.json);
576
+ const tty = isTTY(options);
577
+ let agent;
578
+ try {
579
+ agent = await selectAgent({
580
+ ...options,
581
+ json: jsonMode
582
+ }, tty);
583
+ } catch (error) {
584
+ const message = error instanceof Error ? error.message : String(error);
585
+ if (jsonMode) printJson({
586
+ status: "error",
587
+ code: "AGENT_DETECTION_FAILED",
588
+ error: message
589
+ });
590
+ else console.error(chalk.red(message));
591
+ process.exitCode = 1;
592
+ return;
593
+ }
594
+ const definition = MCP_AGENT_DEFINITIONS[agent];
595
+ if (!jsonMode) console.log(chalk.cyan(`\nSetting up Runtype for ${definition.displayName}…`));
596
+ const configuration = await configureMcpAgent(agent, {
597
+ force: options.force,
598
+ dryRun: options.dryRun
599
+ });
600
+ if (!configuration.configured) {
601
+ if (jsonMode) printJson({
602
+ status: "error",
603
+ code: "MCP_CONFIG_FAILED",
604
+ agent,
605
+ configuration
606
+ });
607
+ else console.error(chalk.red(configuration.error ?? "Could not configure Runtype MCP."));
608
+ process.exitCode = 1;
609
+ return;
610
+ }
611
+ if (!jsonMode) {
612
+ if (options.dryRun) console.log(chalk.green("✓ Would install the user-level Runtype MCP connection."));
613
+ else if (configuration.changed) console.log(chalk.green("✓ Installed the user-level Runtype MCP connection."));
614
+ else console.log(chalk.green("✓ Runtype MCP is already configured at the correct URL."));
615
+ if (configuration.configPath) console.log(chalk.dim(` ${configuration.configPath}`));
616
+ if (configuration.backupPath) console.log(chalk.dim(` Backup: ${configuration.backupPath}`));
617
+ }
618
+ const authentication = await authenticateMcpAgent(agent, {
619
+ login: !jsonMode && tty && options.login !== false,
620
+ dryRun: options.dryRun
621
+ });
622
+ if (!jsonMode && !options.dryRun) {
623
+ if (authentication.authenticated) console.log(chalk.green("✓ MCP OAuth completed."));
624
+ else if (authentication.error) console.log(chalk.yellow(`! ${authentication.error}`));
625
+ else if (authentication.instruction) console.log(chalk.yellow(`! OAuth action required: ${authentication.instruction}`));
626
+ }
627
+ const skills = options.skills === false || options.dryRun ? {
628
+ status: options.dryRun ? "planned" : "skipped",
629
+ installedAgents: [],
630
+ failedAgents: []
631
+ } : await installSkillsForAgent(agent, jsonMode);
632
+ if (!jsonMode && !options.dryRun && skills.status === "error") console.log(chalk.yellow(`! Runtype MCP is configured, but skill installation failed: ${skills.error ?? "unknown error"}`));
633
+ const restartInstruction = getMcpRestartInstruction(agent);
634
+ const nextSteps = [];
635
+ if (options.dryRun) nextSteps.push(`Re-run \`runtype install-mcp --agent ${agent}\` without --dry-run to install skills and MCP.`);
636
+ else {
637
+ if (authentication.instruction) nextSteps.push(authentication.instruction);
638
+ if (authentication.error && !authentication.instruction) nextSteps.push(`Resolve the OAuth blocker reported above, then re-run this command.`);
639
+ if (skills.status === "error") nextSteps.push(`Retry skill installation with \`runtype install-mcp --agent ${agent} --no-login\`.`);
640
+ else if (skills.status === "skipped") nextSteps.push(`Install the Runtype skills with \`runtype skills install --agent ${getMcpSkillsAgentId(agent)}\`.`);
641
+ nextSteps.push("Continue this first session with the Runtype CLI; this running agent may not see the new MCP connection yet.");
642
+ if (authentication.authenticated && skills.status === "installed") nextSteps.push(`Tell the user that Runtype skills and OAuth MCP are installed, then ask them to restart or reload ${definition.displayName}.`);
643
+ else nextSteps.push(`Tell the user that the MCP configuration was installed but setup still has the action(s) above; do not call setup complete yet.`);
644
+ nextSteps.push(`After completing any action above, ${restartInstruction} Then use Runtype MCP first and call get_build_instructions before building.`);
645
+ }
646
+ const partial = Boolean(authentication.error) || skills.status === "error";
647
+ if (jsonMode) printJson({
648
+ status: options.dryRun ? "planned" : partial ? "partial" : authentication.authenticated ? "ok" : "action-required",
649
+ agent,
650
+ configuration,
651
+ authentication,
652
+ skills,
653
+ activation: options.dryRun ? "not-configured" : "restart-required",
654
+ nextSteps
655
+ });
656
+ else {
657
+ console.log(chalk.bold("\nDo exactly this next:"));
658
+ nextSteps.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
659
+ console.log("");
660
+ }
661
+ if (partial) process.exitCode = 1;
662
+ });
663
+ export { getMcpSkillsAgentId as a, getMcpRestartInstruction as i, installMcpCommand, installSkillsForAgent, authenticateMcpAgent as n, selectAgent as o, configureMcpAgent as r, MCP_AGENT_DEFINITIONS as t };
664
+