mcp-twin 1.2.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.
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ /**
3
+ * Config Detector - Auto-detect MCP servers from multiple MCP clients
4
+ *
5
+ * Supports:
6
+ * - Claude Desktop
7
+ * - Claude Code
8
+ * - VS Code (GitHub Copilot)
9
+ * - Cursor
10
+ * - Cline
11
+ * - Windsurf
12
+ *
13
+ * Powered by Prax Chat - https://prax.chat
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.assignPorts = assignPorts;
50
+ exports.detectMCPServers = detectMCPServers;
51
+ exports.detectMCPServersFromClaude = detectMCPServersFromClaude;
52
+ exports.generateTwinConfig = generateTwinConfig;
53
+ exports.autoConfigureTwins = autoConfigureTwins;
54
+ const fs = __importStar(require("fs"));
55
+ const path = __importStar(require("path"));
56
+ const os = __importStar(require("os"));
57
+ /**
58
+ * Get all possible MCP config paths for different clients
59
+ */
60
+ function getAllConfigPaths() {
61
+ const platform = os.platform();
62
+ const home = os.homedir();
63
+ const cwd = process.cwd();
64
+ const paths = [];
65
+ // Claude Desktop
66
+ if (platform === 'darwin') {
67
+ paths.push({
68
+ client: 'Claude Desktop',
69
+ path: path.join(home, 'Library/Application Support/Claude/claude_desktop_config.json')
70
+ });
71
+ }
72
+ else if (platform === 'win32') {
73
+ paths.push({
74
+ client: 'Claude Desktop',
75
+ path: path.join(home, 'AppData/Roaming/Claude/claude_desktop_config.json')
76
+ });
77
+ }
78
+ else {
79
+ paths.push({
80
+ client: 'Claude Desktop',
81
+ path: path.join(home, '.config/claude/claude_desktop_config.json')
82
+ });
83
+ }
84
+ // Claude Code
85
+ paths.push({
86
+ client: 'Claude Code',
87
+ path: path.join(home, '.claude/claude_desktop_config.json')
88
+ });
89
+ // VS Code - workspace
90
+ paths.push({
91
+ client: 'VS Code (workspace)',
92
+ path: path.join(cwd, '.vscode/mcp.json')
93
+ });
94
+ // VS Code - user settings (macOS)
95
+ if (platform === 'darwin') {
96
+ paths.push({
97
+ client: 'VS Code (user)',
98
+ path: path.join(home, 'Library/Application Support/Code/User/settings.json')
99
+ });
100
+ }
101
+ // Cursor - workspace
102
+ paths.push({
103
+ client: 'Cursor (workspace)',
104
+ path: path.join(cwd, '.cursor/mcp.json')
105
+ });
106
+ // Cursor - global
107
+ paths.push({
108
+ client: 'Cursor (global)',
109
+ path: path.join(home, '.cursor/mcp.json')
110
+ });
111
+ // Windsurf
112
+ paths.push({
113
+ client: 'Windsurf',
114
+ path: path.join(home, '.windsurf/mcp.json')
115
+ });
116
+ // Cline (VS Code extension, uses VS Code settings)
117
+ // Already covered by VS Code paths
118
+ return paths;
119
+ }
120
+ /**
121
+ * Get path to Claude desktop config (legacy, for backwards compatibility)
122
+ */
123
+ function getClaudeConfigPath() {
124
+ const platform = os.platform();
125
+ if (platform === 'darwin') {
126
+ return path.join(os.homedir(), 'Library/Application Support/Claude/claude_desktop_config.json');
127
+ }
128
+ else if (platform === 'win32') {
129
+ return path.join(os.homedir(), 'AppData/Roaming/Claude/claude_desktop_config.json');
130
+ }
131
+ else {
132
+ // Linux
133
+ return path.join(os.homedir(), '.config/claude/claude_desktop_config.json');
134
+ }
135
+ }
136
+ /**
137
+ * Extract script path from command args
138
+ */
139
+ function extractScriptPath(command, args) {
140
+ // Common patterns:
141
+ // python3 /path/to/server.py
142
+ // node /path/to/server.js
143
+ // npx ts-node /path/to/server.ts
144
+ for (const arg of args) {
145
+ // Skip flags
146
+ if (arg.startsWith('-'))
147
+ continue;
148
+ // Check if it's a file path
149
+ const expanded = arg.replace('~', os.homedir());
150
+ if (fs.existsSync(expanded)) {
151
+ return expanded;
152
+ }
153
+ // Check common extensions
154
+ if (arg.endsWith('.py') || arg.endsWith('.js') || arg.endsWith('.ts')) {
155
+ return arg.replace('~', os.homedir());
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+ /**
161
+ * Detect language from command
162
+ */
163
+ function detectLanguage(command) {
164
+ const cmd = command.toLowerCase();
165
+ if (cmd.includes('python') || cmd.includes('python3')) {
166
+ return 'python';
167
+ }
168
+ if (cmd.includes('node') || cmd.includes('npx') || cmd.includes('tsx')) {
169
+ return 'node';
170
+ }
171
+ return 'unknown';
172
+ }
173
+ /**
174
+ * Assign deterministic ports based on server name
175
+ */
176
+ function assignPorts(serverName) {
177
+ // Simple hash function
178
+ let hash = 0;
179
+ for (let i = 0; i < serverName.length; i++) {
180
+ const char = serverName.charCodeAt(i);
181
+ hash = ((hash << 5) - hash) + char;
182
+ hash = hash & hash; // Convert to 32bit integer
183
+ }
184
+ // Map to port range 8100-8298 (99 server pairs)
185
+ const basePort = 8100 + (Math.abs(hash) % 100) * 2;
186
+ return [basePort, basePort + 1];
187
+ }
188
+ /**
189
+ * Detect MCP servers from all supported MCP clients
190
+ */
191
+ function detectMCPServers() {
192
+ const allPaths = getAllConfigPaths();
193
+ const servers = [];
194
+ const seen = new Set(); // Dedupe by server name
195
+ for (const { client, path: configPath } of allPaths) {
196
+ if (!fs.existsSync(configPath)) {
197
+ continue;
198
+ }
199
+ try {
200
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
201
+ // Support both mcpServers (Claude) and servers (VS Code/Cursor) formats
202
+ const mcpServers = config.mcpServers || config.servers || {};
203
+ for (const [name, entry] of Object.entries(mcpServers)) {
204
+ // Skip if already seen (first config wins)
205
+ if (seen.has(name))
206
+ continue;
207
+ seen.add(name);
208
+ const args = entry.args || [];
209
+ const scriptPath = extractScriptPath(entry.command, args);
210
+ const language = detectLanguage(entry.command);
211
+ servers.push({
212
+ name,
213
+ command: entry.command,
214
+ args,
215
+ scriptPath,
216
+ language,
217
+ source: client
218
+ });
219
+ }
220
+ }
221
+ catch (err) {
222
+ // Skip invalid configs silently
223
+ continue;
224
+ }
225
+ }
226
+ return servers;
227
+ }
228
+ /**
229
+ * Detect MCP servers from Claude config only (legacy)
230
+ */
231
+ function detectMCPServersFromClaude() {
232
+ const configPath = getClaudeConfigPath();
233
+ if (!fs.existsSync(configPath)) {
234
+ return [];
235
+ }
236
+ try {
237
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
238
+ const servers = [];
239
+ if (!config.mcpServers) {
240
+ return [];
241
+ }
242
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
243
+ const args = entry.args || [];
244
+ const scriptPath = extractScriptPath(entry.command, args);
245
+ const language = detectLanguage(entry.command);
246
+ servers.push({
247
+ name,
248
+ command: entry.command,
249
+ args,
250
+ scriptPath,
251
+ language,
252
+ source: 'Claude Desktop'
253
+ });
254
+ }
255
+ return servers;
256
+ }
257
+ catch (err) {
258
+ return [];
259
+ }
260
+ }
261
+ /**
262
+ * Generate twin config from detected servers
263
+ */
264
+ function generateTwinConfig(servers) {
265
+ const config = {};
266
+ for (const server of servers) {
267
+ if (!server.scriptPath) {
268
+ console.log(`[ConfigDetector] Skipping ${server.name}: no script path found`);
269
+ continue;
270
+ }
271
+ const [portA, portB] = assignPorts(server.name);
272
+ config[server.name] = {
273
+ script: server.scriptPath,
274
+ ports: [portA, portB],
275
+ healthEndpoint: '/health',
276
+ startupTimeout: 10,
277
+ python: server.language === 'python' ? server.command : 'python3'
278
+ };
279
+ }
280
+ return config;
281
+ }
282
+ /**
283
+ * Auto-detect and update twin config
284
+ */
285
+ function autoConfigureTwins() {
286
+ const detected = detectMCPServers();
287
+ const twinConfig = generateTwinConfig(detected);
288
+ return {
289
+ detected: detected.length,
290
+ configured: Object.keys(twinConfig).length,
291
+ servers: Object.keys(twinConfig)
292
+ };
293
+ }
294
+ // CLI test
295
+ if (require.main === module) {
296
+ console.log('Detecting MCP servers...\n');
297
+ const servers = detectMCPServers();
298
+ if (servers.length === 0) {
299
+ console.log('No MCP servers found in Claude config.');
300
+ }
301
+ else {
302
+ console.log(`Found ${servers.length} servers:\n`);
303
+ for (const server of servers) {
304
+ console.log(` ${server.name}`);
305
+ console.log(` Command: ${server.command}`);
306
+ console.log(` Script: ${server.scriptPath || '(not found)'}`);
307
+ console.log(` Language: ${server.language}`);
308
+ console.log(` Ports: ${assignPorts(server.name).join(', ')}`);
309
+ console.log();
310
+ }
311
+ }
312
+ }
313
+ exports.default = {
314
+ detectMCPServers,
315
+ generateTwinConfig,
316
+ autoConfigureTwins,
317
+ assignPorts
318
+ };
319
+ //# sourceMappingURL=config-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-detector.js","sourceRoot":"","sources":["../src/config-detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * MCP Twin Plugin - Entry Point
3
+ * Zero-downtime MCP server updates for Claude Code
4
+ *
5
+ * Powered by Prax - https://prax.chat
6
+ */
7
+ import { getTwinManager, MCPTwinManager } from './twin-manager';
8
+ import { detectMCPServers, generateTwinConfig, autoConfigureTwins } from './config-detector';
9
+ export { MCPTwinManager, getTwinManager };
10
+ export { detectMCPServers, generateTwinConfig, autoConfigureTwins };
11
+ /**
12
+ * Plugin Commands
13
+ */
14
+ export declare const commands: {
15
+ /**
16
+ * /twin start [server]
17
+ */
18
+ start(serverName?: string): Promise<string>;
19
+ /**
20
+ * /twin stop [server]
21
+ */
22
+ stop(serverName?: string): Promise<string>;
23
+ /**
24
+ * /twin reload <server>
25
+ */
26
+ reload(serverName: string): Promise<string>;
27
+ /**
28
+ * /twin swap <server>
29
+ */
30
+ swap(serverName: string): Promise<string>;
31
+ /**
32
+ * /twin status [server]
33
+ */
34
+ status(serverName?: string): Promise<string>;
35
+ /**
36
+ * /twin detect - Auto-detect MCP servers
37
+ */
38
+ detect(): Promise<string>;
39
+ /**
40
+ * /twin help
41
+ */
42
+ help(): string;
43
+ };
44
+ /**
45
+ * Main command router
46
+ */
47
+ export declare function handleCommand(args: string[]): Promise<string>;
48
+ declare const _default: {
49
+ commands: {
50
+ /**
51
+ * /twin start [server]
52
+ */
53
+ start(serverName?: string): Promise<string>;
54
+ /**
55
+ * /twin stop [server]
56
+ */
57
+ stop(serverName?: string): Promise<string>;
58
+ /**
59
+ * /twin reload <server>
60
+ */
61
+ reload(serverName: string): Promise<string>;
62
+ /**
63
+ * /twin swap <server>
64
+ */
65
+ swap(serverName: string): Promise<string>;
66
+ /**
67
+ * /twin status [server]
68
+ */
69
+ status(serverName?: string): Promise<string>;
70
+ /**
71
+ * /twin detect - Auto-detect MCP servers
72
+ */
73
+ detect(): Promise<string>;
74
+ /**
75
+ * /twin help
76
+ */
77
+ help(): string;
78
+ };
79
+ handleCommand: typeof handleCommand;
80
+ getTwinManager: typeof getTwinManager;
81
+ detectMCPServers: typeof detectMCPServers;
82
+ };
83
+ export default _default;
84
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE7F,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAKpE;;GAEG;AACH,eAAO,MAAM,QAAQ;IACnB;;OAEG;uBACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwCjD;;OAEG;sBACqB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsChD;;OAEG;uBACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBjD;;OAEG;qBACoB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB/C;;OAEG;wBACuB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoElD;;OAEG;cACa,OAAO,CAAC,MAAM,CAAC;IAmB/B;;OAEG;YACK,MAAM;CAuBf,CAAC;AAEF;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBnE;;;QAzRC;;WAEG;2BACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAwCjD;;WAEG;0BACqB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAsChD;;WAEG;2BACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAuBjD;;WAEG;yBACoB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAsB/C;;WAEG;4BACuB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAoElD;;WAEG;kBACa,OAAO,CAAC,MAAM,CAAC;QAmB/B;;WAEG;gBACK,MAAM;;;;;;AAoDhB,wBAKE"}
package/dist/index.js ADDED
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Twin Plugin - Entry Point
4
+ * Zero-downtime MCP server updates for Claude Code
5
+ *
6
+ * Powered by Prax - https://prax.chat
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.commands = exports.autoConfigureTwins = exports.generateTwinConfig = exports.detectMCPServers = exports.getTwinManager = exports.MCPTwinManager = void 0;
10
+ exports.handleCommand = handleCommand;
11
+ const twin_manager_1 = require("./twin-manager");
12
+ Object.defineProperty(exports, "getTwinManager", { enumerable: true, get: function () { return twin_manager_1.getTwinManager; } });
13
+ Object.defineProperty(exports, "MCPTwinManager", { enumerable: true, get: function () { return twin_manager_1.MCPTwinManager; } });
14
+ const config_detector_1 = require("./config-detector");
15
+ Object.defineProperty(exports, "detectMCPServers", { enumerable: true, get: function () { return config_detector_1.detectMCPServers; } });
16
+ Object.defineProperty(exports, "generateTwinConfig", { enumerable: true, get: function () { return config_detector_1.generateTwinConfig; } });
17
+ Object.defineProperty(exports, "autoConfigureTwins", { enumerable: true, get: function () { return config_detector_1.autoConfigureTwins; } });
18
+ // Branding
19
+ const PRAX_FOOTER = '\n─────────────────────────────────────────\n⚡ Powered by Prax Chat | prax.chat/mcp-twin';
20
+ /**
21
+ * Plugin Commands
22
+ */
23
+ exports.commands = {
24
+ /**
25
+ * /twin start [server]
26
+ */
27
+ async start(serverName) {
28
+ const manager = (0, twin_manager_1.getTwinManager)();
29
+ if (!serverName) {
30
+ // Show available servers
31
+ const status = await manager.status();
32
+ const available = status.available || [];
33
+ if (available.length === 0) {
34
+ return `No MCP servers configured.
35
+
36
+ Run auto-detect to find servers:
37
+ /twin detect
38
+
39
+ Or add manually:
40
+ /twin add <name> <script-path>`;
41
+ }
42
+ return `Available servers:
43
+ ${available.map((s) => ` - ${s}`).join('\n')}
44
+
45
+ Start twins:
46
+ /twin start <server-name>`;
47
+ }
48
+ const result = await manager.startTwins(serverName);
49
+ if (!result.ok) {
50
+ return `Failed to start twins: ${result.error}`;
51
+ }
52
+ return `Started twins for ${serverName}
53
+
54
+ Server A: port ${result.pidA ? result.statusA : 'failed'} ${result.statusA === 'running' ? '●' : '○'}
55
+ Server B: port ${result.pidB ? result.statusB : 'failed'} ${result.statusB === 'running' ? '●' : '○'}
56
+ Active: A
57
+
58
+ ${result.hint}${PRAX_FOOTER}`;
59
+ },
60
+ /**
61
+ * /twin stop [server]
62
+ */
63
+ async stop(serverName) {
64
+ const manager = (0, twin_manager_1.getTwinManager)();
65
+ if (!serverName) {
66
+ return `Usage: /twin stop <server-name>
67
+
68
+ Or stop all: /twin stop --all`;
69
+ }
70
+ if (serverName === '--all') {
71
+ const status = await manager.status();
72
+ const running = Object.keys(status.twins || {});
73
+ if (running.length === 0) {
74
+ return 'No twins running.';
75
+ }
76
+ const results = [];
77
+ for (const name of running) {
78
+ const result = await manager.stopTwins(name);
79
+ results.push(` ${name}: ${result.ok ? 'stopped' : 'failed'}`);
80
+ }
81
+ return `Stopped all twins:\n${results.join('\n')}`;
82
+ }
83
+ const result = await manager.stopTwins(serverName);
84
+ if (!result.ok) {
85
+ return `Failed to stop: ${result.error}`;
86
+ }
87
+ return `Stopped ${serverName} twins
88
+
89
+ Server A: stopped
90
+ Server B: stopped`;
91
+ },
92
+ /**
93
+ * /twin reload <server>
94
+ */
95
+ async reload(serverName) {
96
+ if (!serverName) {
97
+ return 'Usage: /twin reload <server-name>';
98
+ }
99
+ const manager = (0, twin_manager_1.getTwinManager)();
100
+ const result = await manager.reloadStandby(serverName);
101
+ if (!result.ok) {
102
+ return `Reload failed: ${result.error}
103
+
104
+ ${result.hint || ''}`;
105
+ }
106
+ return `Reloaded ${serverName} standby
107
+
108
+ ${result.reloaded}
109
+ Health: ${result.healthy ? 'passing ●' : 'FAILED ○'}
110
+ Reload count: ${result.reloadCount}
111
+
112
+ ${result.hint}`;
113
+ },
114
+ /**
115
+ * /twin swap <server>
116
+ */
117
+ async swap(serverName) {
118
+ if (!serverName) {
119
+ return 'Usage: /twin swap <server-name>';
120
+ }
121
+ const manager = (0, twin_manager_1.getTwinManager)();
122
+ const result = await manager.swapActive(serverName);
123
+ if (!result.ok) {
124
+ return `Swap failed: ${result.error}
125
+
126
+ ${result.hint || ''}`;
127
+ }
128
+ return `Swapped ${serverName}
129
+
130
+ Previous: ${result.previousActive}
131
+ Now active: ${result.newActive}
132
+
133
+ ${result.hint}${PRAX_FOOTER}`;
134
+ },
135
+ /**
136
+ * /twin status [server]
137
+ */
138
+ async status(serverName) {
139
+ const manager = (0, twin_manager_1.getTwinManager)();
140
+ const result = await manager.status(serverName);
141
+ if (!result.ok) {
142
+ return `Error: ${result.error}`;
143
+ }
144
+ if (serverName) {
145
+ // Detailed single server status
146
+ const { serverA, serverB, active, reloadCount, lastSwap } = result;
147
+ return `${serverName} Twin Status
148
+ ${'═'.repeat(40)}
149
+
150
+ Server A (port ${serverA.port})
151
+ State: ${serverA.state}
152
+ Health: ${serverA.healthy ? 'healthy ●' : 'unhealthy ○'}
153
+ ${serverA.isActive ? '← ACTIVE' : ' standby'}
154
+
155
+ Server B (port ${serverB.port})
156
+ State: ${serverB.state}
157
+ Health: ${serverB.healthy ? 'healthy ●' : 'unhealthy ○'}
158
+ ${serverB.isActive ? '← ACTIVE' : ' standby'}
159
+
160
+ Reloads: ${reloadCount}
161
+ Last swap: ${lastSwap ? new Date(lastSwap).toLocaleTimeString() : 'never'}`;
162
+ }
163
+ // All twins summary
164
+ const twins = result.twins || {};
165
+ const twinNames = Object.keys(twins);
166
+ if (twinNames.length === 0) {
167
+ const available = result.available || [];
168
+ return `No twins running.
169
+
170
+ Available servers:
171
+ ${available.map((s) => ` - ${s}`).join('\n')}
172
+
173
+ Start with: /twin start <server>`;
174
+ }
175
+ let output = `MCP Twin Status
176
+ ${'═'.repeat(40)}
177
+
178
+ `;
179
+ for (const [name, info] of Object.entries(twins)) {
180
+ const activeLabel = info.active.toUpperCase();
181
+ const [healthA, healthB] = info.healthy;
182
+ output += `${name}
183
+ Active: ${activeLabel} (${info.ports[info.active === 'a' ? 0 : 1]}) ${healthA || healthB ? '●' : '○'}
184
+ Standby: ${info.active === 'a' ? 'B' : 'A'} (${info.ports[info.active === 'a' ? 1 : 0]}) ${(info.active === 'a' ? healthB : healthA) ? '●' : '○'}
185
+ Reloads: ${info.reloadCount}
186
+
187
+ `;
188
+ }
189
+ const notRunning = (result.available || []).filter((s) => !twins[s]);
190
+ if (notRunning.length > 0) {
191
+ output += `Not running: ${notRunning.join(', ')}`;
192
+ }
193
+ return output;
194
+ },
195
+ /**
196
+ * /twin detect - Auto-detect MCP servers
197
+ */
198
+ async detect() {
199
+ const result = (0, config_detector_1.autoConfigureTwins)();
200
+ if (result.detected === 0) {
201
+ return `No MCP servers found in Claude config.
202
+
203
+ Add servers manually:
204
+ /twin add <name> <script-path> <port1> <port2>`;
205
+ }
206
+ return `Detected ${result.detected} MCP servers:
207
+ ${result.servers.map(s => ` - ${s}`).join('\n')}
208
+
209
+ ${result.configured} configured for twins.
210
+
211
+ Start twins:
212
+ /twin start <server-name>`;
213
+ },
214
+ /**
215
+ * /twin help
216
+ */
217
+ help() {
218
+ return `MCP Twin - Zero-Downtime Server Updates
219
+ ${'═'.repeat(44)}
220
+
221
+ Commands:
222
+ /twin start [server] Start twin servers
223
+ /twin stop [server] Stop twin servers
224
+ /twin reload <server> Reload standby with new code
225
+ /twin swap <server> Switch traffic to standby
226
+ /twin status [server] Show twin status
227
+ /twin detect Auto-detect MCP servers
228
+ /twin help Show this help
229
+
230
+ Workflow:
231
+ 1. /twin start my-server → Start A (active) + B (standby)
232
+ 2. Edit your server code
233
+ 3. /twin reload my-server → Update standby B
234
+ 4. /twin swap my-server → Switch to B
235
+ 5. Keep coding - no restart needed!
236
+ ${PRAX_FOOTER}
237
+
238
+ Pro features coming soon: prax.chat/mcp-twin/pro`;
239
+ }
240
+ };
241
+ /**
242
+ * Main command router
243
+ */
244
+ async function handleCommand(args) {
245
+ const [subcommand, ...rest] = args;
246
+ switch (subcommand?.toLowerCase()) {
247
+ case 'start':
248
+ return exports.commands.start(rest[0]);
249
+ case 'stop':
250
+ return exports.commands.stop(rest[0]);
251
+ case 'reload':
252
+ return exports.commands.reload(rest[0]);
253
+ case 'swap':
254
+ return exports.commands.swap(rest[0]);
255
+ case 'status':
256
+ return exports.commands.status(rest[0]);
257
+ case 'detect':
258
+ return exports.commands.detect();
259
+ case 'help':
260
+ case undefined:
261
+ return exports.commands.help();
262
+ default:
263
+ return `Unknown command: ${subcommand}\n\nRun /twin help for usage.`;
264
+ }
265
+ }
266
+ exports.default = {
267
+ commands: exports.commands,
268
+ handleCommand,
269
+ getTwinManager: twin_manager_1.getTwinManager,
270
+ detectMCPServers: config_detector_1.detectMCPServers
271
+ };
272
+ //# sourceMappingURL=index.js.map