@ruifung/codemode-bridge 1.0.8 → 1.0.9

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/dist/cli/index.js CHANGED
@@ -19,14 +19,27 @@
19
19
  import { Command } from "commander";
20
20
  import { runServer, listServersCommand, showServerCommand, addServerCommand, removeServerCommand, editServerCommand, configInfoCommand, authLoginCommand, authLogoutCommand, authListCommand, } from "./commands.js";
21
21
  import { getConfigFilePath } from "./config-manager.js";
22
+ import { getExecutorStatus } from "../mcp/executor-status.js";
22
23
  import * as fs from "node:fs";
23
24
  const pkg = JSON.parse(fs.readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
24
25
  const defaultConfigPath = getConfigFilePath();
25
26
  const program = new Command();
27
+ // Add logic to get available executors for version output
28
+ let versionString = pkg.version;
29
+ if (process.argv.includes("-V") || process.argv.includes("--version")) {
30
+ const statuses = await getExecutorStatus();
31
+ const available = statuses
32
+ .filter((s) => s.isAvailable)
33
+ .map((s) => s.type)
34
+ .join(", ");
35
+ if (available) {
36
+ versionString = `${pkg.version} (executors: ${available})`;
37
+ }
38
+ }
26
39
  program
27
40
  .name("codemode-bridge")
28
41
  .description("Code Mode Bridge CLI - Connects to multiple MCP servers and exposes them as a single tool")
29
- .version(pkg.version);
42
+ .version(versionString);
30
43
  // Main 'run' command
31
44
  program
32
45
  .command("run", { isDefault: true })
@@ -57,18 +57,25 @@ function getScriptPaths() {
57
57
  }
58
58
  // ── ContainerExecutor ───────────────────────────────────────────────
59
59
  export class ContainerExecutor {
60
+ runtime;
61
+ image;
62
+ containerUser;
63
+ containerCommand;
64
+ timeout;
65
+ memoryLimit;
66
+ cpuLimit;
67
+ containerId = null;
68
+ process = null;
69
+ readline = null;
70
+ ready = false;
71
+ /** Resolved when the container sends { type: 'ready' } */
72
+ readyResolve = null;
73
+ /** Pending execution — only one at a time */
74
+ pendingExecution = null;
75
+ initPromise = null;
60
76
  constructor(options = {}) {
61
- this.containerId = null;
62
- this.process = null;
63
- this.readline = null;
64
- this.ready = false;
65
- /** Resolved when the container sends { type: 'ready' } */
66
- this.readyResolve = null;
67
- /** Pending execution — only one at a time */
68
- this.pendingExecution = null;
69
- this.initPromise = null;
70
77
  this.timeout = options.timeout ?? 30000;
71
- this.memoryLimit = options.memoryLimit ?? '256m';
78
+ this.memoryLimit = options.memoryLimit ?? '512M';
72
79
  this.cpuLimit = options.cpuLimit ?? 1.0;
73
80
  this.runtime = detectRuntime(options.runtime);
74
81
  // Platform-specific defaults
@@ -171,7 +178,7 @@ export class ContainerExecutor {
171
178
  '--user', this.containerUser,
172
179
  '--memory', this.memoryLimit,
173
180
  `--cpus=${this.cpuLimit}`,
174
- '--pids-limit=64', // limit process spawning
181
+ '--pids-limit=128', // limit process spawning
175
182
  '-v', `${scripts.runner}:/app/container-runner.mjs:ro`, // mount runner script
176
183
  '-v', `${scripts.worker}:/app/container-worker.mjs:ro`, // mount worker script
177
184
  '-w', '/app',
@@ -37,13 +37,15 @@ function getScriptPaths() {
37
37
  }
38
38
  // ── DenoExecutor ────────────────────────────────────────────────────
39
39
  export class DenoExecutor {
40
+ denoPath;
41
+ timeout;
42
+ process = null;
43
+ readline = null;
44
+ ready = false;
45
+ readyResolve = null;
46
+ pendingExecution = null;
47
+ initPromise = null;
40
48
  constructor(options = {}) {
41
- this.process = null;
42
- this.readline = null;
43
- this.ready = false;
44
- this.readyResolve = null;
45
- this.pendingExecution = null;
46
- this.initPromise = null;
47
49
  this.timeout = options.timeout ?? 30000;
48
50
  this.denoPath = detectDeno(options.denoPath);
49
51
  this.init().catch(err => {
@@ -55,9 +55,11 @@ function stringify(value) {
55
55
  * - Explicit serialization boundaries
56
56
  */
57
57
  export class IsolatedVmExecutor {
58
+ isolate;
59
+ context = null;
60
+ metrics = null;
61
+ options;
58
62
  constructor(options = {}) {
59
- this.context = null;
60
- this.metrics = null;
61
63
  this.options = {
62
64
  memoryLimit: options.memoryLimit ?? 128,
63
65
  inspector: options.inspector ?? false,
@@ -9,6 +9,7 @@ import { wrapCode } from "./wrap-code.js";
9
9
  * Runs LLM-generated code in an isolated sandbox with access to tools via codemode.* namespace
10
10
  */
11
11
  export class VM2Executor {
12
+ timeout;
12
13
  constructor(timeout = 30000) {
13
14
  this.timeout = timeout;
14
15
  }
@@ -0,0 +1,6 @@
1
+ export type ExecutorType = 'isolated-vm' | 'container' | 'deno' | 'vm2';
2
+ export interface ExecutorStatus {
3
+ type: ExecutorType;
4
+ isAvailable: boolean;
5
+ }
6
+ export declare function getExecutorStatus(): Promise<ExecutorStatus[]>;
@@ -0,0 +1,58 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { isNode, isDeno, isBun, getNodeMajorVersion } from "../utils/env.js";
3
+ async function isDenoAvailable() {
4
+ return isDeno();
5
+ }
6
+ async function isIsolatedVmAvailable() {
7
+ const majorVersion = getNodeMajorVersion();
8
+ const isEvenVersion = majorVersion > 0 && majorVersion % 2 === 0;
9
+ if (!isNode() || !isEvenVersion) {
10
+ return false;
11
+ }
12
+ try {
13
+ const checkScript = `
14
+ import ivm from 'isolated-vm';
15
+ const isolate = new ivm.Isolate({ memoryLimit: 8 });
16
+ isolate.dispose();
17
+ process.exit(0);
18
+ `;
19
+ const { execSync } = await import('node:child_process');
20
+ execSync(`node -e "${checkScript.replace(/"/g, '\\"').replace(/\n/g, '')}"`, { stdio: 'ignore', timeout: 2000 });
21
+ return true;
22
+ }
23
+ catch (err) {
24
+ return false;
25
+ }
26
+ }
27
+ async function isContainerRuntimeAvailable() {
28
+ for (const cmd of ['docker', 'podman']) {
29
+ try {
30
+ execFileSync(cmd, ['ps'], { stdio: 'ignore', timeout: 2000 });
31
+ return true;
32
+ }
33
+ catch (err) {
34
+ // not available
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+ async function isVM2Available() {
40
+ if (isBun())
41
+ return false;
42
+ try {
43
+ await import('vm2');
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ export async function getExecutorStatus() {
51
+ const statuses = [
52
+ { type: 'deno', isAvailable: await isDenoAvailable() },
53
+ { type: 'isolated-vm', isAvailable: await isIsolatedVmAvailable() },
54
+ { type: 'container', isAvailable: await isContainerRuntimeAvailable() },
55
+ { type: 'vm2', isAvailable: await isVM2Available() },
56
+ ];
57
+ return statuses;
58
+ }
@@ -19,6 +19,15 @@ import { logDebug, logInfo } from "../utils/logger.js";
19
19
  * Supports both pre-registered clients and dynamic client registration (RFC 7591)
20
20
  */
21
21
  class SimpleOAuthProvider {
22
+ config;
23
+ serverUrl;
24
+ static DEFAULT_REDIRECT_URL = 'http://localhost:3000/oauth/callback';
25
+ _tokens;
26
+ _codeVerifier;
27
+ _clientInfo;
28
+ _discoveryState;
29
+ _callbackServer;
30
+ _finishAuthCallback;
22
31
  constructor(config, serverUrl) {
23
32
  this.config = config;
24
33
  this.serverUrl = serverUrl;
@@ -196,15 +205,17 @@ class SimpleOAuthProvider {
196
205
  return undefined;
197
206
  }
198
207
  }
199
- SimpleOAuthProvider.DEFAULT_REDIRECT_URL = 'http://localhost:3000/oauth/callback';
200
208
  /**
201
209
  * MCP Client wrapper using official SDK
202
210
  */
203
211
  export class MCPClient {
212
+ config;
213
+ client;
214
+ transport = null;
215
+ connected = false;
216
+ oauthProvider;
204
217
  constructor(config) {
205
218
  this.config = config;
206
- this.transport = null;
207
- this.connected = false;
208
219
  this.client = new Client({
209
220
  name: `codemode-bridge-client-${config.name}`,
210
221
  version: "1.0.0",
@@ -12,9 +12,11 @@ import { URL } from 'node:url';
12
12
  * Loopback HTTP server that listens for OAuth2 redirect callbacks
13
13
  */
14
14
  export class OAuthCallbackServer {
15
+ server;
16
+ port = 0; // 0 means OS will assign an available port
17
+ host = 'localhost';
18
+ pendingAuthorization;
15
19
  constructor(redirectUrl) {
16
- this.port = 0; // 0 means OS will assign an available port
17
- this.host = 'localhost';
18
20
  // Parse redirect URL to extract host and port
19
21
  if (redirectUrl) {
20
22
  try {
@@ -11,8 +11,10 @@ import { homedir } from 'node:os';
11
11
  * Manages OAuth token storage for MCP server connections
12
12
  */
13
13
  export class TokenPersistence {
14
+ configDir;
15
+ tokenFile;
16
+ storage = {};
14
17
  constructor() {
15
- this.storage = {};
16
18
  this.configDir = join(homedir(), '.config', 'codemode-bridge');
17
19
  this.tokenFile = join(this.configDir, 'mcp-tokens.json');
18
20
  this.loadStorage();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruifung/codemode-bridge",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "MCP bridge that connects to upstream MCP servers and exposes tools via a single codemode tool for orchestration",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -14,8 +14,11 @@
14
14
  "build": "tsc",
15
15
  "prepare": "npm run build",
16
16
  "dev": "tsx src/cli/index.ts",
17
+ "dev:bun": "bun run src/cli/index.ts",
18
+ "dev:deno": "deno run --allow-net --allow-read src/cli/index.ts",
17
19
  "test": "vitest",
18
- "test:e2e": "vitest run src/mcp/e2e-bridge-runner.test.ts --reporter=verbose"
20
+ "test:bun": "bun test",
21
+ "test:deno": "deno run -A npm:vitest"
19
22
  },
20
23
  "keywords": [
21
24
  "mcp",