@willjackson/claude-code-bridge 0.3.0 → 0.4.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/dist/cli.js CHANGED
@@ -1,20 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Bridge,
4
+ BridgeMcpServer,
4
5
  ConnectionState,
5
6
  WebSocketTransport,
6
7
  createLogger,
7
8
  loadConfigSync
8
- } from "./chunk-LUL3SX2F.js";
9
+ } from "./chunk-MHUQYPTB.js";
9
10
 
10
11
  // src/cli/index.ts
11
- import { Command as Command6 } from "commander";
12
+ import { Command as Command7 } from "commander";
12
13
 
13
14
  // src/cli/commands/start.ts
14
15
  import { Command } from "commander";
15
16
  import * as fs from "fs";
16
17
  import * as path from "path";
17
18
  import * as os from "os";
19
+ import { spawn } from "child_process";
18
20
  import { minimatch } from "minimatch";
19
21
 
20
22
  // src/cli/utils.ts
@@ -72,10 +74,10 @@ Received ${signal}, shutting down gracefully...`);
72
74
  };
73
75
  }
74
76
  function handleUnhandledRejections(options = {}) {
75
- const { exit = false, logger: logger7 = console.error } = options;
77
+ const { exit = false, logger: logger8 = console.error } = options;
76
78
  process.on("unhandledRejection", (reason, promise) => {
77
79
  const error = reason instanceof Error ? reason : new Error(String(reason));
78
- logger7("Unhandled promise rejection:", error);
80
+ logger8("Unhandled promise rejection:", error);
79
81
  if (exit) {
80
82
  process.exit(1);
81
83
  }
@@ -296,6 +298,59 @@ function registerHandlers(bridge, config) {
296
298
  };
297
299
  }
298
300
  }
301
+ if (taskData?.action === "list_directory") {
302
+ const dirPath = taskData.path;
303
+ if (!dirPath) {
304
+ return {
305
+ success: false,
306
+ data: { error: "list_directory requires path" }
307
+ };
308
+ }
309
+ const fullPath = path.isAbsolute(dirPath) ? dirPath : path.join(cwd, dirPath);
310
+ const resolvedPath = path.resolve(fullPath);
311
+ const resolvedCwd = path.resolve(cwd);
312
+ if (!resolvedPath.startsWith(resolvedCwd)) {
313
+ return {
314
+ success: false,
315
+ data: { error: "Cannot list directories outside project directory" }
316
+ };
317
+ }
318
+ try {
319
+ if (!fs.existsSync(resolvedPath)) {
320
+ return {
321
+ success: false,
322
+ data: { error: `Directory not found: ${dirPath}` }
323
+ };
324
+ }
325
+ const stats = fs.statSync(resolvedPath);
326
+ if (!stats.isDirectory()) {
327
+ return {
328
+ success: false,
329
+ data: { error: `Not a directory: ${dirPath}` }
330
+ };
331
+ }
332
+ const entries = fs.readdirSync(resolvedPath, { withFileTypes: true });
333
+ const listing = entries.map((entry) => ({
334
+ name: entry.name,
335
+ type: entry.isDirectory() ? "directory" : "file"
336
+ }));
337
+ logger.info({ path: dirPath, count: listing.length }, "Directory listed successfully");
338
+ return {
339
+ success: true,
340
+ data: {
341
+ action: "list_directory",
342
+ path: dirPath,
343
+ entries: listing
344
+ }
345
+ };
346
+ } catch (err) {
347
+ logger.error({ error: err.message, path: dirPath }, "Failed to list directory");
348
+ return {
349
+ success: false,
350
+ data: { error: `Failed to list directory: ${err.message}` }
351
+ };
352
+ }
353
+ }
299
354
  const projectInfo = {
300
355
  cwd,
301
356
  platform: process.platform,
@@ -408,10 +463,12 @@ function buildBridgeConfig(options, globalOptions) {
408
463
  return finalConfig;
409
464
  }
410
465
  async function startBridge(options, globalOptions) {
411
- const { running, pid } = isAlreadyRunning();
412
- if (running) {
413
- console.error(`Bridge is already running (PID: ${pid})`);
414
- process.exit(1);
466
+ if (!process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
467
+ const { running, pid } = isAlreadyRunning();
468
+ if (running) {
469
+ console.error(`Bridge is already running (PID: ${pid})`);
470
+ process.exit(1);
471
+ }
415
472
  }
416
473
  const config = buildBridgeConfig(options, globalOptions);
417
474
  console.log("Starting Claude Code Bridge...");
@@ -423,9 +480,24 @@ async function startBridge(options, globalOptions) {
423
480
  if (config.connect) {
424
481
  console.log(` Connecting to: ${config.connect.url}`);
425
482
  }
426
- if (options.daemon) {
427
- console.log(" Running in daemon mode");
428
- writePidFile(process.pid);
483
+ if (options.daemon && !process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
484
+ const logFile = path.join(os.homedir(), ".claude-bridge", "bridge.log");
485
+ ensureBridgeDir();
486
+ const args = process.argv.slice(2).filter((arg) => arg !== "-d" && arg !== "--daemon");
487
+ const out = fs.openSync(logFile, "a");
488
+ const err = fs.openSync(logFile, "a");
489
+ const child = spawn(process.execPath, [process.argv[1], ...args], {
490
+ detached: true,
491
+ stdio: ["ignore", out, err],
492
+ env: { ...process.env, CLAUDE_BRIDGE_DAEMON_CHILD: "1" }
493
+ });
494
+ child.unref();
495
+ writePidFile(child.pid);
496
+ console.log(` Running in background (PID: ${child.pid})`);
497
+ console.log(` Log file: ${logFile}`);
498
+ console.log(` Use 'claude-bridge status' to check status`);
499
+ console.log(` Use 'claude-bridge stop' to stop the bridge`);
500
+ process.exit(0);
429
501
  }
430
502
  const bridge = new Bridge(config);
431
503
  setupGracefulShutdown({
@@ -434,7 +506,7 @@ async function startBridge(options, globalOptions) {
434
506
  await bridge.stop();
435
507
  logger.info("Bridge stopped");
436
508
  },
437
- afterCleanup: options.daemon ? removePidFile : void 0,
509
+ afterCleanup: process.env.CLAUDE_BRIDGE_DAEMON_CHILD ? removePidFile : void 0,
438
510
  verbose: true,
439
511
  timeout: 1e4
440
512
  });
@@ -451,7 +523,7 @@ async function startBridge(options, globalOptions) {
451
523
  excludePatterns: fileConfig.contextSharing.excludePatterns
452
524
  });
453
525
  }
454
- if (options.daemon) {
526
+ if (process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
455
527
  writePidFile(process.pid);
456
528
  }
457
529
  console.log("Bridge started successfully.");
@@ -461,13 +533,13 @@ async function startBridge(options, globalOptions) {
461
533
  To connect from another bridge:`);
462
534
  console.log(` claude-bridge connect ws://localhost:${config.listen.port}`);
463
535
  }
464
- if (!options.daemon) {
536
+ if (!process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
465
537
  console.log("\nPress Ctrl+C to stop.");
466
538
  }
467
539
  } catch (error) {
468
540
  logger.error({ error: error.message }, "Failed to start bridge");
469
541
  console.error(`Failed to start bridge: ${error.message}`);
470
- if (options.daemon) {
542
+ if (process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
471
543
  removePidFile();
472
544
  }
473
545
  process.exit(1);
@@ -935,11 +1007,140 @@ function createInfoCommand() {
935
1007
  return command;
936
1008
  }
937
1009
 
1010
+ // src/cli/commands/mcp-server.ts
1011
+ import { Command as Command6 } from "commander";
1012
+ import * as fs5 from "fs";
1013
+ import * as path5 from "path";
1014
+ import * as os5 from "os";
1015
+ import { spawn as spawn2 } from "child_process";
1016
+ var logger6 = createLogger("cli:mcp-server");
1017
+ var DEFAULT_DAEMON_PORT = 8766;
1018
+ function getStatusFilePath2() {
1019
+ const bridgeDir = path5.join(os5.homedir(), ".claude-bridge");
1020
+ return path5.join(bridgeDir, "status.json");
1021
+ }
1022
+ function getPidFilePath4() {
1023
+ const bridgeDir = path5.join(os5.homedir(), ".claude-bridge");
1024
+ return path5.join(bridgeDir, "bridge.pid");
1025
+ }
1026
+ function isDaemonRunning() {
1027
+ const pidFile = getPidFilePath4();
1028
+ if (!fs5.existsSync(pidFile)) {
1029
+ return { running: false };
1030
+ }
1031
+ try {
1032
+ const pid = parseInt(fs5.readFileSync(pidFile, "utf-8").trim(), 10);
1033
+ process.kill(pid, 0);
1034
+ const statusFile = getStatusFilePath2();
1035
+ if (fs5.existsSync(statusFile)) {
1036
+ const status = JSON.parse(fs5.readFileSync(statusFile, "utf-8"));
1037
+ return { running: true, pid, port: status.port };
1038
+ }
1039
+ return { running: true, pid };
1040
+ } catch {
1041
+ return { running: false };
1042
+ }
1043
+ }
1044
+ async function waitForDaemon(port, timeoutMs) {
1045
+ const startTime = Date.now();
1046
+ while (Date.now() - startTime < timeoutMs) {
1047
+ const { running, port: runningPort } = isDaemonRunning();
1048
+ if (running && (runningPort === port || runningPort === void 0)) {
1049
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
1050
+ return;
1051
+ }
1052
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
1053
+ }
1054
+ throw new Error(`Daemon did not start within ${timeoutMs}ms`);
1055
+ }
1056
+ function ensureBridgeDir2() {
1057
+ const bridgeDir = path5.join(os5.homedir(), ".claude-bridge");
1058
+ if (!fs5.existsSync(bridgeDir)) {
1059
+ fs5.mkdirSync(bridgeDir, { recursive: true });
1060
+ }
1061
+ }
1062
+ async function ensureDaemonRunning(port) {
1063
+ const { running, pid, port: runningPort } = isDaemonRunning();
1064
+ if (running) {
1065
+ const effectivePort = runningPort ?? port;
1066
+ console.error(`[MCP] Bridge daemon already running (PID: ${pid}, port: ${effectivePort})`);
1067
+ return effectivePort;
1068
+ }
1069
+ console.error("[MCP] Starting bridge daemon...");
1070
+ ensureBridgeDir2();
1071
+ const logFile = path5.join(os5.homedir(), ".claude-bridge", "bridge.log");
1072
+ const out = fs5.openSync(logFile, "a");
1073
+ const err = fs5.openSync(logFile, "a");
1074
+ const cliPath = process.argv[1];
1075
+ const child = spawn2(process.execPath, [cliPath, "start", "--daemon", "--port", String(port)], {
1076
+ detached: true,
1077
+ stdio: ["ignore", out, err]
1078
+ });
1079
+ child.unref();
1080
+ console.error(`[MCP] Daemon starting (PID: ${child.pid})`);
1081
+ try {
1082
+ await waitForDaemon(port, 5e3);
1083
+ console.error(`[MCP] Daemon ready on port ${port}`);
1084
+ return port;
1085
+ } catch (error) {
1086
+ console.error(`[MCP] Warning: Could not verify daemon is ready: ${error.message}`);
1087
+ return port;
1088
+ }
1089
+ }
1090
+ async function startMcpServer(options, _globalOptions) {
1091
+ let bridgeUrl = options.connect ?? `ws://localhost:${DEFAULT_DAEMON_PORT}`;
1092
+ if (!options.connect) {
1093
+ try {
1094
+ const port = await ensureDaemonRunning(DEFAULT_DAEMON_PORT);
1095
+ bridgeUrl = `ws://localhost:${port}`;
1096
+ } catch (error) {
1097
+ console.error(`[MCP] Warning: Could not ensure daemon is running: ${error.message}`);
1098
+ }
1099
+ }
1100
+ const config = {
1101
+ bridgeUrl,
1102
+ name: options.name ?? "claude-bridge",
1103
+ version: "0.4.0",
1104
+ instanceName: `mcp-server-${process.pid}`,
1105
+ taskTimeout: 6e4
1106
+ };
1107
+ const server = new BridgeMcpServer(config);
1108
+ setupGracefulShutdown({
1109
+ cleanup: async () => {
1110
+ console.error("[MCP] Shutting down...");
1111
+ await server.stop();
1112
+ },
1113
+ verbose: false,
1114
+ timeout: 5e3
1115
+ });
1116
+ handleUnhandledRejections({
1117
+ exit: true,
1118
+ logger: (msg, err) => {
1119
+ console.error(`[MCP] ${msg}: ${err.message}`);
1120
+ logger6.error({ error: err.message }, msg);
1121
+ }
1122
+ });
1123
+ try {
1124
+ await server.start();
1125
+ } catch (error) {
1126
+ console.error(`[MCP] Failed to start MCP server: ${error.message}`);
1127
+ process.exit(1);
1128
+ }
1129
+ }
1130
+ function createMcpServerCommand() {
1131
+ const command = new Command6("mcp-server");
1132
+ command.description("Start the MCP server for Claude Code integration").option("-c, --connect <url>", `Bridge WebSocket URL (default: ws://localhost:${DEFAULT_DAEMON_PORT})`).option("--name <name>", "MCP server name (default: claude-bridge)").action(async (options) => {
1133
+ const globalOptions = command.parent?.opts();
1134
+ await startMcpServer(options, globalOptions);
1135
+ });
1136
+ return command;
1137
+ }
1138
+
938
1139
  // src/cli/index.ts
939
1140
  var VERSION = "0.1.0";
940
- var logger6 = createLogger("cli");
1141
+ var logger7 = createLogger("cli");
941
1142
  function createProgram() {
942
- const program = new Command6();
1143
+ const program = new Command7();
943
1144
  program.name("claude-bridge").description("Bidirectional communication system for Claude Code instances across environments").version(VERSION, "-V, --version", "Output the version number").option("-v, --verbose", "Enable verbose logging").option("--config <path>", "Path to config file");
944
1145
  program.hook("preAction", (thisCommand) => {
945
1146
  const opts = thisCommand.opts();
@@ -959,10 +1160,11 @@ async function main() {
959
1160
  program.addCommand(createStatusCommand());
960
1161
  program.addCommand(createConnectCommand());
961
1162
  program.addCommand(createInfoCommand());
1163
+ program.addCommand(createMcpServerCommand());
962
1164
  await program.parseAsync(process.argv);
963
1165
  }
964
1166
  main().catch((error) => {
965
- logger6.error({ err: error }, "CLI error");
1167
+ logger7.error({ err: error }, "CLI error");
966
1168
  process.exit(1);
967
1169
  });
968
1170
  export {