@willjackson/claude-code-bridge 0.3.0 → 0.5.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.
- package/README.md +103 -43
- package/dist/{chunk-LUL3SX2F.js → chunk-MHUQYPTB.js} +409 -5
- package/dist/chunk-MHUQYPTB.js.map +1 -0
- package/dist/cli.js +234 -19
- 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,34 @@ 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
|
+
if (options.launchClaude) {
|
|
501
|
+
console.log("\nLaunching Claude Code...");
|
|
502
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
503
|
+
const { spawnSync } = await import("child_process");
|
|
504
|
+
const result = spawnSync("claude", [], {
|
|
505
|
+
stdio: "inherit",
|
|
506
|
+
shell: true
|
|
507
|
+
});
|
|
508
|
+
process.exit(result.status ?? 0);
|
|
509
|
+
}
|
|
510
|
+
process.exit(0);
|
|
429
511
|
}
|
|
430
512
|
const bridge = new Bridge(config);
|
|
431
513
|
setupGracefulShutdown({
|
|
@@ -434,7 +516,7 @@ async function startBridge(options, globalOptions) {
|
|
|
434
516
|
await bridge.stop();
|
|
435
517
|
logger.info("Bridge stopped");
|
|
436
518
|
},
|
|
437
|
-
afterCleanup:
|
|
519
|
+
afterCleanup: process.env.CLAUDE_BRIDGE_DAEMON_CHILD ? removePidFile : void 0,
|
|
438
520
|
verbose: true,
|
|
439
521
|
timeout: 1e4
|
|
440
522
|
});
|
|
@@ -451,7 +533,7 @@ async function startBridge(options, globalOptions) {
|
|
|
451
533
|
excludePatterns: fileConfig.contextSharing.excludePatterns
|
|
452
534
|
});
|
|
453
535
|
}
|
|
454
|
-
if (
|
|
536
|
+
if (process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
|
|
455
537
|
writePidFile(process.pid);
|
|
456
538
|
}
|
|
457
539
|
console.log("Bridge started successfully.");
|
|
@@ -461,13 +543,13 @@ async function startBridge(options, globalOptions) {
|
|
|
461
543
|
To connect from another bridge:`);
|
|
462
544
|
console.log(` claude-bridge connect ws://localhost:${config.listen.port}`);
|
|
463
545
|
}
|
|
464
|
-
if (!
|
|
546
|
+
if (!process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
|
|
465
547
|
console.log("\nPress Ctrl+C to stop.");
|
|
466
548
|
}
|
|
467
549
|
} catch (error) {
|
|
468
550
|
logger.error({ error: error.message }, "Failed to start bridge");
|
|
469
551
|
console.error(`Failed to start bridge: ${error.message}`);
|
|
470
|
-
if (
|
|
552
|
+
if (process.env.CLAUDE_BRIDGE_DAEMON_CHILD) {
|
|
471
553
|
removePidFile();
|
|
472
554
|
}
|
|
473
555
|
process.exit(1);
|
|
@@ -475,7 +557,10 @@ To connect from another bridge:`);
|
|
|
475
557
|
}
|
|
476
558
|
function createStartCommand() {
|
|
477
559
|
const command = new Command("start");
|
|
478
|
-
command.description("Start the bridge server").option("-p, --port <port>", "Port to listen on (default: 8765)").option("-h, --host <host>", "Host to bind to (default: 0.0.0.0)").option("-c, --connect <url>", "URL to connect to on startup (e.g., ws://localhost:8765)").option("-d, --daemon", "Run in background").option("--with-handlers", "Enable file reading and task handling capabilities").action(async (options) => {
|
|
560
|
+
command.description("Start the bridge server").option("-p, --port <port>", "Port to listen on (default: 8765)").option("-h, --host <host>", "Host to bind to (default: 0.0.0.0)").option("-c, --connect <url>", "URL to connect to on startup (e.g., ws://localhost:8765)").option("-d, --daemon", "Run in background").option("--with-handlers", "Enable file reading and task handling capabilities").option("--launch-claude", "Start bridge daemon and launch Claude Code").action(async (options) => {
|
|
561
|
+
if (options.launchClaude) {
|
|
562
|
+
options.daemon = true;
|
|
563
|
+
}
|
|
479
564
|
const globalOptions = command.parent?.opts();
|
|
480
565
|
await startBridge(options, globalOptions);
|
|
481
566
|
});
|
|
@@ -935,11 +1020,140 @@ function createInfoCommand() {
|
|
|
935
1020
|
return command;
|
|
936
1021
|
}
|
|
937
1022
|
|
|
1023
|
+
// src/cli/commands/mcp-server.ts
|
|
1024
|
+
import { Command as Command6 } from "commander";
|
|
1025
|
+
import * as fs5 from "fs";
|
|
1026
|
+
import * as path5 from "path";
|
|
1027
|
+
import * as os5 from "os";
|
|
1028
|
+
import { spawn as spawn2 } from "child_process";
|
|
1029
|
+
var logger6 = createLogger("cli:mcp-server");
|
|
1030
|
+
var DEFAULT_DAEMON_PORT = 8766;
|
|
1031
|
+
function getStatusFilePath2() {
|
|
1032
|
+
const bridgeDir = path5.join(os5.homedir(), ".claude-bridge");
|
|
1033
|
+
return path5.join(bridgeDir, "status.json");
|
|
1034
|
+
}
|
|
1035
|
+
function getPidFilePath4() {
|
|
1036
|
+
const bridgeDir = path5.join(os5.homedir(), ".claude-bridge");
|
|
1037
|
+
return path5.join(bridgeDir, "bridge.pid");
|
|
1038
|
+
}
|
|
1039
|
+
function isDaemonRunning() {
|
|
1040
|
+
const pidFile = getPidFilePath4();
|
|
1041
|
+
if (!fs5.existsSync(pidFile)) {
|
|
1042
|
+
return { running: false };
|
|
1043
|
+
}
|
|
1044
|
+
try {
|
|
1045
|
+
const pid = parseInt(fs5.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
1046
|
+
process.kill(pid, 0);
|
|
1047
|
+
const statusFile = getStatusFilePath2();
|
|
1048
|
+
if (fs5.existsSync(statusFile)) {
|
|
1049
|
+
const status = JSON.parse(fs5.readFileSync(statusFile, "utf-8"));
|
|
1050
|
+
return { running: true, pid, port: status.port };
|
|
1051
|
+
}
|
|
1052
|
+
return { running: true, pid };
|
|
1053
|
+
} catch {
|
|
1054
|
+
return { running: false };
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async function waitForDaemon(port, timeoutMs) {
|
|
1058
|
+
const startTime = Date.now();
|
|
1059
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
1060
|
+
const { running, port: runningPort } = isDaemonRunning();
|
|
1061
|
+
if (running && (runningPort === port || runningPort === void 0)) {
|
|
1062
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
1066
|
+
}
|
|
1067
|
+
throw new Error(`Daemon did not start within ${timeoutMs}ms`);
|
|
1068
|
+
}
|
|
1069
|
+
function ensureBridgeDir2() {
|
|
1070
|
+
const bridgeDir = path5.join(os5.homedir(), ".claude-bridge");
|
|
1071
|
+
if (!fs5.existsSync(bridgeDir)) {
|
|
1072
|
+
fs5.mkdirSync(bridgeDir, { recursive: true });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async function ensureDaemonRunning(port) {
|
|
1076
|
+
const { running, pid, port: runningPort } = isDaemonRunning();
|
|
1077
|
+
if (running) {
|
|
1078
|
+
const effectivePort = runningPort ?? port;
|
|
1079
|
+
console.error(`[MCP] Bridge daemon already running (PID: ${pid}, port: ${effectivePort})`);
|
|
1080
|
+
return effectivePort;
|
|
1081
|
+
}
|
|
1082
|
+
console.error("[MCP] Starting bridge daemon...");
|
|
1083
|
+
ensureBridgeDir2();
|
|
1084
|
+
const logFile = path5.join(os5.homedir(), ".claude-bridge", "bridge.log");
|
|
1085
|
+
const out = fs5.openSync(logFile, "a");
|
|
1086
|
+
const err = fs5.openSync(logFile, "a");
|
|
1087
|
+
const cliPath = process.argv[1];
|
|
1088
|
+
const child = spawn2(process.execPath, [cliPath, "start", "--daemon", "--port", String(port)], {
|
|
1089
|
+
detached: true,
|
|
1090
|
+
stdio: ["ignore", out, err]
|
|
1091
|
+
});
|
|
1092
|
+
child.unref();
|
|
1093
|
+
console.error(`[MCP] Daemon starting (PID: ${child.pid})`);
|
|
1094
|
+
try {
|
|
1095
|
+
await waitForDaemon(port, 5e3);
|
|
1096
|
+
console.error(`[MCP] Daemon ready on port ${port}`);
|
|
1097
|
+
return port;
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.error(`[MCP] Warning: Could not verify daemon is ready: ${error.message}`);
|
|
1100
|
+
return port;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async function startMcpServer(options, _globalOptions) {
|
|
1104
|
+
let bridgeUrl = options.connect ?? `ws://localhost:${DEFAULT_DAEMON_PORT}`;
|
|
1105
|
+
if (!options.connect) {
|
|
1106
|
+
try {
|
|
1107
|
+
const port = await ensureDaemonRunning(DEFAULT_DAEMON_PORT);
|
|
1108
|
+
bridgeUrl = `ws://localhost:${port}`;
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
console.error(`[MCP] Warning: Could not ensure daemon is running: ${error.message}`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
const config = {
|
|
1114
|
+
bridgeUrl,
|
|
1115
|
+
name: options.name ?? "claude-bridge",
|
|
1116
|
+
version: "0.4.0",
|
|
1117
|
+
instanceName: `mcp-server-${process.pid}`,
|
|
1118
|
+
taskTimeout: 6e4
|
|
1119
|
+
};
|
|
1120
|
+
const server = new BridgeMcpServer(config);
|
|
1121
|
+
setupGracefulShutdown({
|
|
1122
|
+
cleanup: async () => {
|
|
1123
|
+
console.error("[MCP] Shutting down...");
|
|
1124
|
+
await server.stop();
|
|
1125
|
+
},
|
|
1126
|
+
verbose: false,
|
|
1127
|
+
timeout: 5e3
|
|
1128
|
+
});
|
|
1129
|
+
handleUnhandledRejections({
|
|
1130
|
+
exit: true,
|
|
1131
|
+
logger: (msg, err) => {
|
|
1132
|
+
console.error(`[MCP] ${msg}: ${err.message}`);
|
|
1133
|
+
logger6.error({ error: err.message }, msg);
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
try {
|
|
1137
|
+
await server.start();
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
console.error(`[MCP] Failed to start MCP server: ${error.message}`);
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function createMcpServerCommand() {
|
|
1144
|
+
const command = new Command6("mcp-server");
|
|
1145
|
+
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) => {
|
|
1146
|
+
const globalOptions = command.parent?.opts();
|
|
1147
|
+
await startMcpServer(options, globalOptions);
|
|
1148
|
+
});
|
|
1149
|
+
return command;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
938
1152
|
// src/cli/index.ts
|
|
939
1153
|
var VERSION = "0.1.0";
|
|
940
|
-
var
|
|
1154
|
+
var logger7 = createLogger("cli");
|
|
941
1155
|
function createProgram() {
|
|
942
|
-
const program = new
|
|
1156
|
+
const program = new Command7();
|
|
943
1157
|
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
1158
|
program.hook("preAction", (thisCommand) => {
|
|
945
1159
|
const opts = thisCommand.opts();
|
|
@@ -959,10 +1173,11 @@ async function main() {
|
|
|
959
1173
|
program.addCommand(createStatusCommand());
|
|
960
1174
|
program.addCommand(createConnectCommand());
|
|
961
1175
|
program.addCommand(createInfoCommand());
|
|
1176
|
+
program.addCommand(createMcpServerCommand());
|
|
962
1177
|
await program.parseAsync(process.argv);
|
|
963
1178
|
}
|
|
964
1179
|
main().catch((error) => {
|
|
965
|
-
|
|
1180
|
+
logger7.error({ err: error }, "CLI error");
|
|
966
1181
|
process.exit(1);
|
|
967
1182
|
});
|
|
968
1183
|
export {
|