@ruifung/codemode-bridge 1.0.3-1
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.
- package/LICENSE +202 -0
- package/README.md +378 -0
- package/dist/cli/commands.d.ts +70 -0
- package/dist/cli/commands.js +436 -0
- package/dist/cli/config-manager.d.ts +53 -0
- package/dist/cli/config-manager.js +142 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.js +165 -0
- package/dist/executor/container-executor.d.ts +81 -0
- package/dist/executor/container-executor.js +351 -0
- package/dist/executor/executor-test-suite.d.ts +22 -0
- package/dist/executor/executor-test-suite.js +395 -0
- package/dist/executor/isolated-vm-executor.d.ts +78 -0
- package/dist/executor/isolated-vm-executor.js +368 -0
- package/dist/executor/vm2-executor.d.ts +21 -0
- package/dist/executor/vm2-executor.js +109 -0
- package/dist/executor/wrap-code.d.ts +52 -0
- package/dist/executor/wrap-code.js +80 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mcp/config.d.ts +44 -0
- package/dist/mcp/config.js +102 -0
- package/dist/mcp/e2e-bridge-test-suite.d.ts +28 -0
- package/dist/mcp/e2e-bridge-test-suite.js +429 -0
- package/dist/mcp/executor.d.ts +31 -0
- package/dist/mcp/executor.js +121 -0
- package/dist/mcp/mcp-adapter.d.ts +12 -0
- package/dist/mcp/mcp-adapter.js +49 -0
- package/dist/mcp/mcp-client.d.ts +85 -0
- package/dist/mcp/mcp-client.js +441 -0
- package/dist/mcp/oauth-handler.d.ts +33 -0
- package/dist/mcp/oauth-handler.js +138 -0
- package/dist/mcp/server.d.ts +25 -0
- package/dist/mcp/server.js +322 -0
- package/dist/mcp/token-persistence.d.ts +57 -0
- package/dist/mcp/token-persistence.js +131 -0
- package/dist/utils/logger.d.ts +44 -0
- package/dist/utils/logger.js +123 -0
- package/package.json +56 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Commands for Code Mode Bridge
|
|
3
|
+
*
|
|
4
|
+
* Implements subcommands for managing and running the bridge
|
|
5
|
+
*/
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { loadConfig, saveConfig, addServer, removeServer, updateServer, getServer, listServers, validateServer, getConfigFilePath, } from "./config-manager.js";
|
|
10
|
+
import { startCodeModeBridgeServer } from "../mcp/server.js";
|
|
11
|
+
import { getServerConfig } from "../mcp/config.js";
|
|
12
|
+
import { initializeLogger, logInfo, logError, flushStderrBuffer } from "../utils/logger.js";
|
|
13
|
+
import { tokenPersistence } from "../mcp/token-persistence.js";
|
|
14
|
+
import { MCPClient } from "../mcp/mcp-client.js";
|
|
15
|
+
/**
|
|
16
|
+
* Run the bridge server
|
|
17
|
+
*/
|
|
18
|
+
export async function runServer(configPath, servers, debug) {
|
|
19
|
+
try {
|
|
20
|
+
// Initialize logger with debug mode if requested
|
|
21
|
+
initializeLogger(debug);
|
|
22
|
+
console.error(chalk.cyan("\n🚀 Code Mode Bridge"));
|
|
23
|
+
console.error(chalk.cyan("====================\n"));
|
|
24
|
+
// Load the bridge configuration
|
|
25
|
+
const bridgeConfig = loadConfig(configPath);
|
|
26
|
+
logInfo(`Loaded config from: ${getConfigFilePath(configPath)}`, { component: 'CLI' });
|
|
27
|
+
logInfo(`Found ${Object.keys(bridgeConfig.servers).length} configured servers`, { component: 'CLI' });
|
|
28
|
+
// Determine which servers to connect to
|
|
29
|
+
let serverNames = [];
|
|
30
|
+
if (servers && servers.length > 0) {
|
|
31
|
+
serverNames = servers;
|
|
32
|
+
logInfo(`Loading servers: ${serverNames.join(", ")}`, { component: 'CLI' });
|
|
33
|
+
}
|
|
34
|
+
else if (Object.keys(bridgeConfig.servers).length > 0) {
|
|
35
|
+
serverNames = Object.keys(bridgeConfig.servers);
|
|
36
|
+
logInfo(`No servers specified, loading all configured servers: ${serverNames.join(", ")}`, { component: 'CLI' });
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error(chalk.yellow("ℹ") + " No servers configured\n");
|
|
40
|
+
}
|
|
41
|
+
// Load server configurations
|
|
42
|
+
const serverConfigs = [];
|
|
43
|
+
for (const serverName of serverNames) {
|
|
44
|
+
try {
|
|
45
|
+
const serverConfig = getServerConfig(bridgeConfig, serverName);
|
|
46
|
+
serverConfigs.push(serverConfig);
|
|
47
|
+
logInfo(`Loaded ${serverName}`, { component: 'CLI' });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
logError(`Failed to load ${serverName}`, err instanceof Error ? err : { error: String(err) });
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
logInfo(`Starting bridge with ${serverConfigs.length} server(s)`, { component: 'CLI' });
|
|
55
|
+
// Start the MCP bridge server
|
|
56
|
+
await startCodeModeBridgeServer(serverConfigs);
|
|
57
|
+
// Flush buffered stderr output from stdio tools now that Bridge is fully running
|
|
58
|
+
flushStderrBuffer();
|
|
59
|
+
logInfo("Bridge is running!", { component: 'CLI' });
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
logError("Error", error instanceof Error ? error : { error: String(error) });
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* List all configured servers
|
|
68
|
+
*/
|
|
69
|
+
export function listServersCommand(configPath) {
|
|
70
|
+
try {
|
|
71
|
+
const config = loadConfig(configPath);
|
|
72
|
+
const servers = listServers(config);
|
|
73
|
+
if (servers.length === 0) {
|
|
74
|
+
console.log(chalk.yellow("No servers configured.\n"));
|
|
75
|
+
console.log(`To add a server, use:\n`);
|
|
76
|
+
console.log(` ${chalk.cyan("codemode-bridge config add <name> --type stdio --command <command>")}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log(chalk.cyan("\nConfigured Servers:") + "\n");
|
|
80
|
+
for (const { name, entry } of servers) {
|
|
81
|
+
console.log(chalk.bold(name));
|
|
82
|
+
if (entry.type === "stdio") {
|
|
83
|
+
console.log(` Type: ${chalk.blue(entry.type)}`);
|
|
84
|
+
console.log(` Command: ${entry.command}`);
|
|
85
|
+
if (entry.args && entry.args.length > 0) {
|
|
86
|
+
console.log(` Args: ${entry.args.join(" ")}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (entry.type === "http") {
|
|
90
|
+
console.log(` Type: ${chalk.blue(entry.type)}`);
|
|
91
|
+
console.log(` URL: ${entry.url}`);
|
|
92
|
+
}
|
|
93
|
+
if (entry.env && Object.keys(entry.env).length > 0) {
|
|
94
|
+
console.log(` Env: ${JSON.stringify(entry.env)}`);
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
console.log(`Config file: ${chalk.gray(getConfigFilePath(configPath))}\n`);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error(chalk.red("✗") +
|
|
102
|
+
" Error: " +
|
|
103
|
+
(error instanceof Error ? error.message : String(error)));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Show a specific server configuration
|
|
109
|
+
*/
|
|
110
|
+
export function showServerCommand(name, configPath) {
|
|
111
|
+
try {
|
|
112
|
+
const config = loadConfig(configPath);
|
|
113
|
+
const entry = getServer(config, name);
|
|
114
|
+
if (!entry) {
|
|
115
|
+
console.error(chalk.red("✗") + ` Server "${name}" not found`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
console.log(chalk.cyan(`\nServer: ${name}\n`));
|
|
119
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error(chalk.red("✗") +
|
|
124
|
+
" Error: " +
|
|
125
|
+
(error instanceof Error ? error.message : String(error)));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Add a new server configuration
|
|
131
|
+
*/
|
|
132
|
+
export function addServerCommand(name, options, configPath) {
|
|
133
|
+
try {
|
|
134
|
+
const entry = {
|
|
135
|
+
type: options.type,
|
|
136
|
+
};
|
|
137
|
+
if (options.type === "stdio") {
|
|
138
|
+
if (!options.command) {
|
|
139
|
+
console.error(chalk.red("✗") + ' Missing --command for stdio server');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
entry.command = options.command;
|
|
143
|
+
if (options.args) {
|
|
144
|
+
entry.args = options.args;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (options.type === "http") {
|
|
148
|
+
if (!options.url) {
|
|
149
|
+
console.error(chalk.red("✗") + ' Missing --url for http server');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
entry.url = options.url;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.error(chalk.red("✗") + ` Invalid type: "${options.type}"`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
if (options.env) {
|
|
159
|
+
entry.env = options.env;
|
|
160
|
+
}
|
|
161
|
+
// Validate
|
|
162
|
+
const validation = validateServer(entry);
|
|
163
|
+
if (!validation.valid) {
|
|
164
|
+
console.error(chalk.red("✗") + " Validation failed:");
|
|
165
|
+
for (const error of validation.errors) {
|
|
166
|
+
console.error(" " + error);
|
|
167
|
+
}
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
// Load, add, and save
|
|
171
|
+
const config = loadConfig(configPath);
|
|
172
|
+
addServer(config, name, entry);
|
|
173
|
+
saveConfig(config, configPath);
|
|
174
|
+
console.log(chalk.green("✓") + ` Added server "${name}"\n`);
|
|
175
|
+
showServerCommand(name, configPath);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error(chalk.red("✗") +
|
|
179
|
+
" Error: " +
|
|
180
|
+
(error instanceof Error ? error.message : String(error)));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Remove a server configuration
|
|
186
|
+
*/
|
|
187
|
+
export function removeServerCommand(name, configPath) {
|
|
188
|
+
try {
|
|
189
|
+
const config = loadConfig(configPath);
|
|
190
|
+
removeServer(config, name);
|
|
191
|
+
saveConfig(config, configPath);
|
|
192
|
+
console.log(chalk.green("✓") + ` Removed server "${name}"\n`);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error(chalk.red("✗") +
|
|
196
|
+
" Error: " +
|
|
197
|
+
(error instanceof Error ? error.message : String(error)));
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Edit a server configuration
|
|
203
|
+
*/
|
|
204
|
+
export function editServerCommand(name, options, configPath) {
|
|
205
|
+
try {
|
|
206
|
+
const config = loadConfig(configPath);
|
|
207
|
+
const entry = getServer(config, name);
|
|
208
|
+
if (!entry) {
|
|
209
|
+
console.error(chalk.red("✗") + ` Server "${name}" not found`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
// Update fields
|
|
213
|
+
if (options.type)
|
|
214
|
+
entry.type = options.type;
|
|
215
|
+
if (options.command)
|
|
216
|
+
entry.command = options.command;
|
|
217
|
+
if (options.args)
|
|
218
|
+
entry.args = options.args;
|
|
219
|
+
if (options.url)
|
|
220
|
+
entry.url = options.url;
|
|
221
|
+
if (options.env)
|
|
222
|
+
entry.env = options.env;
|
|
223
|
+
// Validate
|
|
224
|
+
const validation = validateServer(entry);
|
|
225
|
+
if (!validation.valid) {
|
|
226
|
+
console.error(chalk.red("✗") + " Validation failed:");
|
|
227
|
+
for (const error of validation.errors) {
|
|
228
|
+
console.error(" " + error);
|
|
229
|
+
}
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
// Save
|
|
233
|
+
updateServer(config, name, entry);
|
|
234
|
+
saveConfig(config, configPath);
|
|
235
|
+
console.log(chalk.green("✓") + ` Updated server "${name}"\n`);
|
|
236
|
+
showServerCommand(name, configPath);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error(chalk.red("✗") +
|
|
240
|
+
" Error: " +
|
|
241
|
+
(error instanceof Error ? error.message : String(error)));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Show config file information
|
|
247
|
+
*/
|
|
248
|
+
export function configInfoCommand(configPath) {
|
|
249
|
+
const filePath = getConfigFilePath(configPath);
|
|
250
|
+
const dir = path.dirname(filePath);
|
|
251
|
+
const exists = fs.existsSync(filePath);
|
|
252
|
+
console.log(chalk.cyan("\nConfiguration Information\n"));
|
|
253
|
+
console.log(`Config file: ${chalk.bold(filePath)}`);
|
|
254
|
+
console.log(`Directory: ${chalk.bold(dir)}`);
|
|
255
|
+
console.log(`Status: ${exists ? chalk.green("exists") : chalk.yellow("will be created")}`);
|
|
256
|
+
if (configPath) {
|
|
257
|
+
console.log(`\n${chalk.yellow("ℹ")} Using custom config path`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.log(`\n${chalk.yellow("ℹ")} Using default config path`);
|
|
261
|
+
}
|
|
262
|
+
console.log();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Login command for OAuth servers
|
|
266
|
+
*
|
|
267
|
+
* Usage: codemode-bridge auth login <server-name>
|
|
268
|
+
*
|
|
269
|
+
* Starts the bridge connected only to the specified OAuth server.
|
|
270
|
+
* This allows the OAuth flow to complete naturally during connection.
|
|
271
|
+
*/
|
|
272
|
+
export async function authLoginCommand(serverName, configPath) {
|
|
273
|
+
try {
|
|
274
|
+
// Initialize logger
|
|
275
|
+
initializeLogger(false);
|
|
276
|
+
// Load config and validate server
|
|
277
|
+
const config = loadConfig(configPath);
|
|
278
|
+
const serverEntry = getServer(config, serverName);
|
|
279
|
+
if (!serverEntry) {
|
|
280
|
+
console.error(chalk.red("✗") + ` Server "${serverName}" not found`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
// Check if server has OAuth configured
|
|
284
|
+
if (!serverEntry.oauth) {
|
|
285
|
+
console.error(chalk.red("✗") + ` Server "${serverName}" does not have OAuth configured`);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
// Only HTTP servers support OAuth
|
|
289
|
+
if (serverEntry.type !== "http") {
|
|
290
|
+
console.error(chalk.red("✗") + ` OAuth is only supported for HTTP servers (${serverName} is ${serverEntry.type})`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
if (!serverEntry.url) {
|
|
294
|
+
console.error(chalk.red("✗") + ` Server "${serverName}" is missing URL configuration`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
console.log(chalk.cyan(`\nAuthenticating with ${chalk.bold(serverName)}...\n`));
|
|
298
|
+
// Create an MCP client for OAuth authentication
|
|
299
|
+
// This will handle the OAuth flow without fully connecting the client
|
|
300
|
+
const mcpConfig = {
|
|
301
|
+
name: serverName,
|
|
302
|
+
type: serverEntry.type,
|
|
303
|
+
url: serverEntry.url,
|
|
304
|
+
oauth: serverEntry.oauth,
|
|
305
|
+
};
|
|
306
|
+
const client = new MCPClient(mcpConfig);
|
|
307
|
+
// Authenticate via OAuth - this triggers browser authorization and token exchange
|
|
308
|
+
logInfo(`Starting OAuth authentication for ${serverName}`, { component: 'CLI' });
|
|
309
|
+
await client.authenticateOAuth();
|
|
310
|
+
// OAuth flow is complete and tokens are saved
|
|
311
|
+
console.log(chalk.green('\n✓ Authentication successful. Tokens have been saved.\n'));
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
logError(`Failed to complete OAuth authentication for ${serverName}`, error instanceof Error ? error : { error: String(error) });
|
|
316
|
+
console.error(chalk.red("\n✗ Authentication failed"));
|
|
317
|
+
if (error instanceof Error) {
|
|
318
|
+
console.error(chalk.red(error.message));
|
|
319
|
+
}
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Logout command for OAuth servers
|
|
325
|
+
*
|
|
326
|
+
* Usage: codemode-bridge auth logout <server-name>
|
|
327
|
+
*
|
|
328
|
+
* Clears all stored authentication data (tokens and client info) for the server.
|
|
329
|
+
*/
|
|
330
|
+
export function authLogoutCommand(serverName, configPath) {
|
|
331
|
+
try {
|
|
332
|
+
// Load config and get server
|
|
333
|
+
const config = loadConfig(configPath);
|
|
334
|
+
const serverEntry = getServer(config, serverName);
|
|
335
|
+
if (!serverEntry) {
|
|
336
|
+
console.error(chalk.red("✗") + ` Server "${serverName}" not found`);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
// Check if server has OAuth configured
|
|
340
|
+
if (!serverEntry.oauth) {
|
|
341
|
+
console.error(chalk.red("✗") + ` Server "${serverName}" does not have OAuth configured`);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
// Get the server URL for token persistence
|
|
345
|
+
let serverUrl;
|
|
346
|
+
if (serverEntry.type === "http" && serverEntry.url) {
|
|
347
|
+
serverUrl = serverEntry.url;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// For stdio servers, use server name as key
|
|
351
|
+
serverUrl = serverName;
|
|
352
|
+
}
|
|
353
|
+
// Clear all auth data including client information
|
|
354
|
+
tokenPersistence.clearAll(serverUrl);
|
|
355
|
+
logInfo(`Cleared all authentication data for ${serverName}`, { component: 'CLI' });
|
|
356
|
+
console.log(chalk.green(`✓ Logged out from ${serverName}`));
|
|
357
|
+
console.log(chalk.cyan(`\nAll tokens and client information have been cleared.`));
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
logError(`Failed to logout from ${serverName}`, error instanceof Error ? error : { error: String(error) });
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* List command for OAuth servers
|
|
366
|
+
*
|
|
367
|
+
* Usage: codemode-bridge auth list
|
|
368
|
+
*
|
|
369
|
+
* Shows all OAuth-enabled servers and their authentication status.
|
|
370
|
+
*/
|
|
371
|
+
export function authListCommand(configPath) {
|
|
372
|
+
try {
|
|
373
|
+
// Load config
|
|
374
|
+
const config = loadConfig(configPath);
|
|
375
|
+
// Find all OAuth-enabled servers
|
|
376
|
+
const oauthServers = [];
|
|
377
|
+
for (const [name, entry] of Object.entries(config.servers || {})) {
|
|
378
|
+
if (entry.oauth) {
|
|
379
|
+
// Determine server URL for token lookup
|
|
380
|
+
let serverUrl;
|
|
381
|
+
if (entry.type === "http" && entry.url) {
|
|
382
|
+
serverUrl = entry.url;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
serverUrl = name;
|
|
386
|
+
}
|
|
387
|
+
// Get token status
|
|
388
|
+
const tokenStatus = tokenPersistence.getTokenStatus(serverUrl);
|
|
389
|
+
let status;
|
|
390
|
+
if (tokenStatus.exists && !tokenStatus.isExpired) {
|
|
391
|
+
status = 'authenticated';
|
|
392
|
+
}
|
|
393
|
+
else if (tokenStatus.exists && tokenStatus.isExpired) {
|
|
394
|
+
status = 'expired';
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
status = 'needs login';
|
|
398
|
+
}
|
|
399
|
+
oauthServers.push({
|
|
400
|
+
name,
|
|
401
|
+
entry,
|
|
402
|
+
serverUrl,
|
|
403
|
+
status,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Display results
|
|
408
|
+
if (oauthServers.length === 0) {
|
|
409
|
+
console.log(chalk.yellow("No OAuth-enabled servers configured.\n"));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
console.log(chalk.cyan("\nOAuth Servers:\n"));
|
|
413
|
+
for (const server of oauthServers) {
|
|
414
|
+
const name = chalk.bold(server.name);
|
|
415
|
+
let statusColor;
|
|
416
|
+
switch (server.status) {
|
|
417
|
+
case 'authenticated':
|
|
418
|
+
statusColor = chalk.green;
|
|
419
|
+
break;
|
|
420
|
+
case 'expired':
|
|
421
|
+
statusColor = chalk.yellow;
|
|
422
|
+
break;
|
|
423
|
+
default:
|
|
424
|
+
statusColor = chalk.gray;
|
|
425
|
+
}
|
|
426
|
+
console.log(`${name} (${server.entry.type})${chalk.dim(` - ${statusColor(server.status)}`)}`);
|
|
427
|
+
}
|
|
428
|
+
console.log();
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
console.error(chalk.red("✗") +
|
|
432
|
+
" Error: " +
|
|
433
|
+
(error instanceof Error ? error.message : String(error)));
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager for Code Mode Bridge
|
|
3
|
+
*
|
|
4
|
+
* Manages the bridge configuration stored in .config/codemode-bridge/mcp.json
|
|
5
|
+
* Provides utilities to load, save, and manipulate the configuration
|
|
6
|
+
*/
|
|
7
|
+
import type { MCPJsonConfig, MCPServerConfigEntry } from "../mcp/config.js";
|
|
8
|
+
/**
|
|
9
|
+
* Get the default config directory for the current platform
|
|
10
|
+
*/
|
|
11
|
+
export declare function getDefaultConfigDir(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Get the full path to the mcp.json config file
|
|
14
|
+
*/
|
|
15
|
+
export declare function getConfigFilePath(overridePath?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Load the configuration file, creating it if it doesn't exist
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadConfig(configPath?: string): MCPJsonConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Save the configuration file
|
|
22
|
+
*/
|
|
23
|
+
export declare function saveConfig(config: MCPJsonConfig, configPath?: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Add a server to the configuration
|
|
26
|
+
*/
|
|
27
|
+
export declare function addServer(config: MCPJsonConfig, name: string, entry: MCPServerConfigEntry): MCPJsonConfig;
|
|
28
|
+
/**
|
|
29
|
+
* Remove a server from the configuration
|
|
30
|
+
*/
|
|
31
|
+
export declare function removeServer(config: MCPJsonConfig, name: string): MCPJsonConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Update a server in the configuration
|
|
34
|
+
*/
|
|
35
|
+
export declare function updateServer(config: MCPJsonConfig, name: string, entry: MCPServerConfigEntry): MCPJsonConfig;
|
|
36
|
+
/**
|
|
37
|
+
* Get a server from the configuration
|
|
38
|
+
*/
|
|
39
|
+
export declare function getServer(config: MCPJsonConfig, name: string): MCPServerConfigEntry | null;
|
|
40
|
+
/**
|
|
41
|
+
* List all servers in the configuration
|
|
42
|
+
*/
|
|
43
|
+
export declare function listServers(config: MCPJsonConfig): {
|
|
44
|
+
name: string;
|
|
45
|
+
entry: MCPServerConfigEntry;
|
|
46
|
+
}[];
|
|
47
|
+
/**
|
|
48
|
+
* Validate a server configuration entry
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateServer(entry: MCPServerConfigEntry): {
|
|
51
|
+
valid: boolean;
|
|
52
|
+
errors: string[];
|
|
53
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager for Code Mode Bridge
|
|
3
|
+
*
|
|
4
|
+
* Manages the bridge configuration stored in .config/codemode-bridge/mcp.json
|
|
5
|
+
* Provides utilities to load, save, and manipulate the configuration
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
/**
|
|
10
|
+
* Get the default config directory for the current platform
|
|
11
|
+
*/
|
|
12
|
+
export function getDefaultConfigDir() {
|
|
13
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd();
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
return path.join(homeDir, ".config", "codemode-bridge");
|
|
16
|
+
}
|
|
17
|
+
else if (process.platform === "darwin") {
|
|
18
|
+
return path.join(homeDir, ".config", "codemode-bridge");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// Linux
|
|
22
|
+
return path.join(homeDir, ".config", "codemode-bridge");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the full path to the mcp.json config file
|
|
27
|
+
*/
|
|
28
|
+
export function getConfigFilePath(overridePath) {
|
|
29
|
+
if (overridePath) {
|
|
30
|
+
return path.resolve(overridePath);
|
|
31
|
+
}
|
|
32
|
+
return path.join(getDefaultConfigDir(), "mcp.json");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load the configuration file, creating it if it doesn't exist
|
|
36
|
+
*/
|
|
37
|
+
export function loadConfig(configPath) {
|
|
38
|
+
const filePath = getConfigFilePath(configPath);
|
|
39
|
+
if (!fs.existsSync(filePath)) {
|
|
40
|
+
// Create default config
|
|
41
|
+
const defaultConfig = {
|
|
42
|
+
servers: {},
|
|
43
|
+
};
|
|
44
|
+
saveConfig(defaultConfig, configPath);
|
|
45
|
+
return defaultConfig;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw new Error(`Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Save the configuration file
|
|
57
|
+
*/
|
|
58
|
+
export function saveConfig(config, configPath) {
|
|
59
|
+
const filePath = getConfigFilePath(configPath);
|
|
60
|
+
const dir = path.dirname(filePath);
|
|
61
|
+
// Create directory if it doesn't exist
|
|
62
|
+
if (!fs.existsSync(dir)) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new Error(`Failed to save config file at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Add a server to the configuration
|
|
74
|
+
*/
|
|
75
|
+
export function addServer(config, name, entry) {
|
|
76
|
+
if (config.servers[name]) {
|
|
77
|
+
throw new Error(`Server "${name}" already exists`);
|
|
78
|
+
}
|
|
79
|
+
config.servers[name] = entry;
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove a server from the configuration
|
|
84
|
+
*/
|
|
85
|
+
export function removeServer(config, name) {
|
|
86
|
+
if (!config.servers[name]) {
|
|
87
|
+
throw new Error(`Server "${name}" not found`);
|
|
88
|
+
}
|
|
89
|
+
delete config.servers[name];
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Update a server in the configuration
|
|
94
|
+
*/
|
|
95
|
+
export function updateServer(config, name, entry) {
|
|
96
|
+
if (!config.servers[name]) {
|
|
97
|
+
throw new Error(`Server "${name}" not found`);
|
|
98
|
+
}
|
|
99
|
+
config.servers[name] = entry;
|
|
100
|
+
return config;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get a server from the configuration
|
|
104
|
+
*/
|
|
105
|
+
export function getServer(config, name) {
|
|
106
|
+
return config.servers[name] || null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* List all servers in the configuration
|
|
110
|
+
*/
|
|
111
|
+
export function listServers(config) {
|
|
112
|
+
return Object.entries(config.servers).map(([name, entry]) => ({
|
|
113
|
+
name,
|
|
114
|
+
entry,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Validate a server configuration entry
|
|
119
|
+
*/
|
|
120
|
+
export function validateServer(entry) {
|
|
121
|
+
const errors = [];
|
|
122
|
+
if (!entry.type) {
|
|
123
|
+
errors.push("Missing 'type' field (must be 'stdio' or 'http')");
|
|
124
|
+
}
|
|
125
|
+
else if (entry.type !== "stdio" && entry.type !== "http") {
|
|
126
|
+
errors.push(`Invalid type: "${entry.type}" (must be 'stdio' or 'http')`);
|
|
127
|
+
}
|
|
128
|
+
if (entry.type === "stdio") {
|
|
129
|
+
if (!entry.command) {
|
|
130
|
+
errors.push("Missing 'command' field for stdio server");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (entry.type === "http") {
|
|
134
|
+
if (!entry.url) {
|
|
135
|
+
errors.push("Missing 'url' field for http server");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
valid: errors.length === 0,
|
|
140
|
+
errors,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Code Mode Bridge CLI
|
|
4
|
+
*
|
|
5
|
+
* Main entry point for the command-line interface
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* codemode-bridge run [options] - Start the bridge server
|
|
9
|
+
* codemode-bridge config list [options] - List configured servers
|
|
10
|
+
* codemode-bridge config show <name> - Show a server configuration
|
|
11
|
+
* codemode-bridge config add <name> - Add a new server
|
|
12
|
+
* codemode-bridge config remove <name> - Remove a server
|
|
13
|
+
* codemode-bridge config edit <name> - Edit a server
|
|
14
|
+
* codemode-bridge config info - Show config file information
|
|
15
|
+
* codemode-bridge auth list [options] - List OAuth-enabled servers and status
|
|
16
|
+
* codemode-bridge auth login <name> - Prepare to login to an OAuth server
|
|
17
|
+
* codemode-bridge auth logout <name> - Logout from an OAuth server
|
|
18
|
+
*/
|
|
19
|
+
export {};
|