@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/README.md +75 -39
- package/dist/{chunk-LUL3SX2F.js → chunk-MHUQYPTB.js} +409 -5
- package/dist/chunk-MHUQYPTB.js.map +1 -0
- package/dist/cli.js +220 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +74 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-LUL3SX2F.js.map +0 -1
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-
|
|
9
|
+
} from "./chunk-MHUQYPTB.js";
|
|
9
10
|
|
|
10
11
|
// src/cli/index.ts
|
|
11
|
-
import { Command as
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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:
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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
|
|
1141
|
+
var logger7 = createLogger("cli");
|
|
941
1142
|
function createProgram() {
|
|
942
|
-
const program = new
|
|
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
|
-
|
|
1167
|
+
logger7.error({ err: error }, "CLI error");
|
|
966
1168
|
process.exit(1);
|
|
967
1169
|
});
|
|
968
1170
|
export {
|