@dolusoft/claude-collab 1.11.0 → 1.11.2
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 +63 -55
- package/dist/cli.js +80 -90
- package/dist/cli.js.map +1 -1
- package/dist/mcp-main.js +80 -90
- package/dist/mcp-main.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp-main.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
4
|
import os, { tmpdir } from 'os';
|
|
5
|
-
import { execFile, spawn } from 'child_process';
|
|
5
|
+
import { execSync, execFile, spawn } from 'child_process';
|
|
6
6
|
import { EventEmitter } from 'events';
|
|
7
7
|
import { unlinkSync } from 'fs';
|
|
8
8
|
import { join } from 'path';
|
|
@@ -10,7 +10,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
10
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
|
|
13
|
-
// src/infrastructure/
|
|
13
|
+
// src/infrastructure/mesh/protocol.ts
|
|
14
14
|
function serialize(msg) {
|
|
15
15
|
return JSON.stringify(msg);
|
|
16
16
|
}
|
|
@@ -262,9 +262,9 @@ var InjectionQueue = class extends EventEmitter {
|
|
|
262
262
|
};
|
|
263
263
|
var injectionQueue = new InjectionQueue();
|
|
264
264
|
|
|
265
|
-
// src/infrastructure/
|
|
265
|
+
// src/infrastructure/mesh/mesh-node.ts
|
|
266
266
|
var FIXED_PORT = 12345;
|
|
267
|
-
var
|
|
267
|
+
var MeshNode = class {
|
|
268
268
|
server = null;
|
|
269
269
|
myName = "";
|
|
270
270
|
running = false;
|
|
@@ -351,7 +351,7 @@ var P2PNode = class {
|
|
|
351
351
|
this.wsToName.delete(ws);
|
|
352
352
|
if (this.peerConnections.get(name) === ws) {
|
|
353
353
|
this.peerConnections.delete(name);
|
|
354
|
-
console.error(`[
|
|
354
|
+
console.error(`[mesh] disconnected from peer: ${name}`);
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
});
|
|
@@ -365,7 +365,7 @@ var P2PNode = class {
|
|
|
365
365
|
async ask(toPeer, content, format) {
|
|
366
366
|
const ws = this.peerConnections.get(toPeer);
|
|
367
367
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
368
|
-
throw new Error(`Peer "${toPeer}" is not connected. Use
|
|
368
|
+
throw new Error(`Peer "${toPeer}" is not connected. Use status() to see who's online.`);
|
|
369
369
|
}
|
|
370
370
|
const questionId = v4();
|
|
371
371
|
this.sentQuestions.set(questionId, { toPeer, content, askedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -425,7 +425,7 @@ var P2PNode = class {
|
|
|
425
425
|
this.sendToWs(ws, answerMsg);
|
|
426
426
|
} else {
|
|
427
427
|
this.pendingOutboundAnswers.set(senderName, answerMsg);
|
|
428
|
-
console.error(`[
|
|
428
|
+
console.error(`[mesh] "${senderName}" is offline, answer queued for delivery on reconnect`);
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
injectionQueue.notifyReplied();
|
|
@@ -499,13 +499,27 @@ var P2PNode = class {
|
|
|
499
499
|
// ---------------------------------------------------------------------------
|
|
500
500
|
// Private: server startup
|
|
501
501
|
// ---------------------------------------------------------------------------
|
|
502
|
-
startServer() {
|
|
502
|
+
async startServer() {
|
|
503
|
+
try {
|
|
504
|
+
await this.tryBind();
|
|
505
|
+
} catch (err) {
|
|
506
|
+
const nodeErr = err;
|
|
507
|
+
if (nodeErr.code === "EADDRINUSE") {
|
|
508
|
+
console.error(`[mesh] port ${FIXED_PORT} in use \u2014 killing existing process`);
|
|
509
|
+
await killProcessOnPort(FIXED_PORT);
|
|
510
|
+
await this.tryBind();
|
|
511
|
+
} else {
|
|
512
|
+
throw err;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
tryBind() {
|
|
503
517
|
return new Promise((resolve, reject) => {
|
|
504
518
|
const wss = new WebSocketServer({ port: FIXED_PORT });
|
|
505
519
|
wss.once("listening", () => {
|
|
506
520
|
this.server = wss;
|
|
507
521
|
this.running = true;
|
|
508
|
-
console.error(`[
|
|
522
|
+
console.error(`[mesh] listening on port ${FIXED_PORT} as "${this.myName}"`);
|
|
509
523
|
this.attachServerHandlers(wss);
|
|
510
524
|
resolve();
|
|
511
525
|
});
|
|
@@ -531,16 +545,16 @@ var P2PNode = class {
|
|
|
531
545
|
this.wsToName.delete(ws);
|
|
532
546
|
if (this.peerConnections.get(name) === ws) {
|
|
533
547
|
this.peerConnections.delete(name);
|
|
534
|
-
console.error(`[
|
|
548
|
+
console.error(`[mesh] peer disconnected (inbound): ${name}`);
|
|
535
549
|
}
|
|
536
550
|
}
|
|
537
551
|
});
|
|
538
552
|
ws.on("error", (err) => {
|
|
539
|
-
console.error("[
|
|
553
|
+
console.error("[mesh] inbound ws error:", err.message);
|
|
540
554
|
});
|
|
541
555
|
});
|
|
542
556
|
wss.on("error", (err) => {
|
|
543
|
-
console.error("[
|
|
557
|
+
console.error("[mesh] server error:", err.message);
|
|
544
558
|
});
|
|
545
559
|
}
|
|
546
560
|
// ---------------------------------------------------------------------------
|
|
@@ -564,7 +578,7 @@ var P2PNode = class {
|
|
|
564
578
|
this.wsToName.set(ws, name);
|
|
565
579
|
this.peerIPs.set(name, ip);
|
|
566
580
|
this.connectingPeers.delete(name);
|
|
567
|
-
console.error(`[
|
|
581
|
+
console.error(`[mesh] peer registered: ${name} @ ${ip}`);
|
|
568
582
|
this.deliverPendingAnswer(name, ws);
|
|
569
583
|
}
|
|
570
584
|
/** Connect outbound to a peer discovered via PEER_LIST or PEER_ANNOUNCE. */
|
|
@@ -589,12 +603,12 @@ var P2PNode = class {
|
|
|
589
603
|
this.wsToName.delete(ws);
|
|
590
604
|
if (this.peerConnections.get(peerName) === ws) {
|
|
591
605
|
this.peerConnections.delete(peerName);
|
|
592
|
-
console.error(`[
|
|
606
|
+
console.error(`[mesh] disconnected from mesh peer: ${peerName}`);
|
|
593
607
|
}
|
|
594
608
|
}
|
|
595
609
|
});
|
|
596
610
|
ws.on("error", (err) => {
|
|
597
|
-
console.error(`[
|
|
611
|
+
console.error(`[mesh] connect to "${name}" @ ${ip} failed: ${err.message}`);
|
|
598
612
|
this.connectingPeers.delete(name);
|
|
599
613
|
});
|
|
600
614
|
}
|
|
@@ -611,7 +625,7 @@ var P2PNode = class {
|
|
|
611
625
|
}
|
|
612
626
|
this.registerPeer(msg.name, remoteIp, ws);
|
|
613
627
|
this.sendToWs(ws, { type: "HELLO_ACK", name: this.myName });
|
|
614
|
-
console.error(`[
|
|
628
|
+
console.error(`[mesh] peer joined (inbound): ${msg.name}`);
|
|
615
629
|
this.afterHandshake(msg.name, ws);
|
|
616
630
|
return;
|
|
617
631
|
}
|
|
@@ -626,7 +640,7 @@ var P2PNode = class {
|
|
|
626
640
|
this.peerConnections.set(msg.name, ws);
|
|
627
641
|
this.wsToName.set(ws, msg.name);
|
|
628
642
|
this.connectingPeers.delete(msg.name);
|
|
629
|
-
console.error(`[
|
|
643
|
+
console.error(`[mesh] connected to mesh peer: ${msg.name}`);
|
|
630
644
|
this.deliverPendingAnswer(msg.name, ws);
|
|
631
645
|
this.afterHandshake(msg.name, ws);
|
|
632
646
|
}
|
|
@@ -634,7 +648,7 @@ var P2PNode = class {
|
|
|
634
648
|
case "PEER_LIST":
|
|
635
649
|
for (const peer of msg.peers) {
|
|
636
650
|
if (peer.name !== this.myName && !this.peerConnections.has(peer.name)) {
|
|
637
|
-
console.error(`[
|
|
651
|
+
console.error(`[mesh] connecting to ${peer.name} @ ${peer.ip} via PEER_LIST`);
|
|
638
652
|
this.peerIPs.set(peer.name, peer.ip);
|
|
639
653
|
this.connectMeshPeer(peer.name, peer.ip);
|
|
640
654
|
}
|
|
@@ -642,7 +656,7 @@ var P2PNode = class {
|
|
|
642
656
|
break;
|
|
643
657
|
case "PEER_ANNOUNCE":
|
|
644
658
|
if (msg.name !== this.myName && !this.peerConnections.has(msg.name)) {
|
|
645
|
-
console.error(`[
|
|
659
|
+
console.error(`[mesh] connecting to ${msg.name} @ ${msg.ip} via PEER_ANNOUNCE`);
|
|
646
660
|
this.peerIPs.set(msg.name, msg.ip);
|
|
647
661
|
this.connectMeshPeer(msg.name, msg.ip);
|
|
648
662
|
}
|
|
@@ -694,7 +708,7 @@ var P2PNode = class {
|
|
|
694
708
|
if (pending) {
|
|
695
709
|
this.pendingOutboundAnswers.delete(peerName);
|
|
696
710
|
this.sendToWs(ws, pending);
|
|
697
|
-
console.error(`[
|
|
711
|
+
console.error(`[mesh] delivered queued answer to "${peerName}" after reconnect`);
|
|
698
712
|
}
|
|
699
713
|
}
|
|
700
714
|
sendToWs(ws, msg) {
|
|
@@ -719,6 +733,34 @@ var P2PNode = class {
|
|
|
719
733
|
});
|
|
720
734
|
}
|
|
721
735
|
};
|
|
736
|
+
async function killProcessOnPort(port) {
|
|
737
|
+
try {
|
|
738
|
+
if (process.platform === "win32") {
|
|
739
|
+
const out = execSync(
|
|
740
|
+
`netstat -ano | findstr ":${port} "`,
|
|
741
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
742
|
+
);
|
|
743
|
+
const pids = /* @__PURE__ */ new Set();
|
|
744
|
+
for (const line of out.split("\n")) {
|
|
745
|
+
const parts = line.trim().split(/\s+/);
|
|
746
|
+
if (parts.length >= 5 && parts[3] === "LISTENING" && parts[4]) {
|
|
747
|
+
pids.add(parts[4]);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
for (const pid of pids) {
|
|
751
|
+
try {
|
|
752
|
+
execSync(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
|
|
753
|
+
console.error(`[mesh] killed PID ${pid} on port ${port}`);
|
|
754
|
+
} catch {
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
execSync(`fuser -k ${port}/tcp`, { stdio: "ignore" });
|
|
759
|
+
}
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
762
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
763
|
+
}
|
|
722
764
|
var CONNECT_DESCRIPTION = `Connect to another Claude instance by their IP address.
|
|
723
765
|
|
|
724
766
|
WHEN TO USE:
|
|
@@ -821,7 +863,7 @@ function registerAskTool(server, client) {
|
|
|
821
863
|
return {
|
|
822
864
|
content: [{
|
|
823
865
|
type: "text",
|
|
824
|
-
text: "
|
|
866
|
+
text: "Node is not ready yet. Wait a moment and try again."
|
|
825
867
|
}],
|
|
826
868
|
isError: true
|
|
827
869
|
};
|
|
@@ -902,7 +944,7 @@ function registerReplyTool(server, client) {
|
|
|
902
944
|
return {
|
|
903
945
|
content: [{
|
|
904
946
|
type: "text",
|
|
905
|
-
text: "
|
|
947
|
+
text: "Node is not ready yet. Wait a moment and try again."
|
|
906
948
|
}],
|
|
907
949
|
isError: true
|
|
908
950
|
};
|
|
@@ -929,68 +971,6 @@ function registerReplyTool(server, client) {
|
|
|
929
971
|
});
|
|
930
972
|
}
|
|
931
973
|
|
|
932
|
-
// src/presentation/mcp/tools/peers.tool.ts
|
|
933
|
-
var PEERS_DESCRIPTION = `List all currently active peer connections.
|
|
934
|
-
|
|
935
|
-
WHEN TO USE:
|
|
936
|
-
- Before calling ask() \u2014 to confirm the target peer is online and get their exact name
|
|
937
|
-
- After connect() \u2014 to verify the connection succeeded and the mesh formed
|
|
938
|
-
- When a peer seems unreachable \u2014 to check if they are still connected
|
|
939
|
-
|
|
940
|
-
WHAT IT SHOWS:
|
|
941
|
-
- Your own name and port
|
|
942
|
-
- All peers with an active direct connection
|
|
943
|
-
|
|
944
|
-
IF NO PEERS ARE LISTED:
|
|
945
|
-
- Use connect(ip) to connect to a peer
|
|
946
|
-
- Ask your teammate to run status() to get their IP`;
|
|
947
|
-
function registerPeersTool(server, client) {
|
|
948
|
-
server.tool("peers", PEERS_DESCRIPTION, {}, async () => {
|
|
949
|
-
const info = client.getInfo();
|
|
950
|
-
const myName = info.teamName ?? "(starting...)";
|
|
951
|
-
const myPort = info.port ?? "?";
|
|
952
|
-
const connected = info.connectedPeers;
|
|
953
|
-
if (!client.isConnected) {
|
|
954
|
-
return {
|
|
955
|
-
content: [{
|
|
956
|
-
type: "text",
|
|
957
|
-
text: [
|
|
958
|
-
`P2P server is not running yet (port ${myPort} may be in use).`,
|
|
959
|
-
`Check the MCP process logs for the error details.`
|
|
960
|
-
].join("\n")
|
|
961
|
-
}],
|
|
962
|
-
isError: true
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
if (connected.length === 0) {
|
|
966
|
-
return {
|
|
967
|
-
content: [{
|
|
968
|
-
type: "text",
|
|
969
|
-
text: [
|
|
970
|
-
`You are "${myName}" (listening on port ${myPort}).`,
|
|
971
|
-
`No peers connected yet.`,
|
|
972
|
-
``,
|
|
973
|
-
`Use connect(ip) to connect to a peer.`,
|
|
974
|
-
`Ask your teammate to run status() to get their IP.`
|
|
975
|
-
].join("\n")
|
|
976
|
-
}]
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
const peerIPs = info.peerIPs ?? {};
|
|
980
|
-
const list = connected.map((name) => {
|
|
981
|
-
const ip = peerIPs[name] ? ` (${peerIPs[name]})` : "";
|
|
982
|
-
return ` \u2022 ${name}${ip}`;
|
|
983
|
-
}).join("\n");
|
|
984
|
-
return {
|
|
985
|
-
content: [{
|
|
986
|
-
type: "text",
|
|
987
|
-
text: `You are "${myName}" (port ${myPort}). Connected peers (${connected.length}):
|
|
988
|
-
${list}`
|
|
989
|
-
}]
|
|
990
|
-
};
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
|
|
994
974
|
// src/presentation/mcp/tools/history.tool.ts
|
|
995
975
|
var HISTORY_DESCRIPTION = `Show all questions and answers exchanged this session \u2014 both sent and received.
|
|
996
976
|
|
|
@@ -1059,12 +1039,23 @@ function registerStatusTool(server, client) {
|
|
|
1059
1039
|
};
|
|
1060
1040
|
}
|
|
1061
1041
|
let ips = [];
|
|
1062
|
-
if (client instanceof
|
|
1042
|
+
if (client instanceof MeshNode) {
|
|
1063
1043
|
ips = client.getLocalIps();
|
|
1064
1044
|
}
|
|
1065
1045
|
const ipLine = ips.length > 0 ? ips.join(", ") : "(could not detect \u2014 check your network interface)";
|
|
1066
|
-
const
|
|
1067
|
-
const
|
|
1046
|
+
const peerIPs = info.peerIPs ?? {};
|
|
1047
|
+
const connected = info.connectedPeers;
|
|
1048
|
+
let peersSection;
|
|
1049
|
+
if (connected.length === 0) {
|
|
1050
|
+
peersSection = "No peers connected yet. Share your IP so others can connect(ip).";
|
|
1051
|
+
} else {
|
|
1052
|
+
const list = connected.map((n) => {
|
|
1053
|
+
const ip = peerIPs[n] ? ` (${peerIPs[n]})` : "";
|
|
1054
|
+
return ` \u2022 ${n}${ip}`;
|
|
1055
|
+
}).join("\n");
|
|
1056
|
+
peersSection = `Connected peers (${connected.length}):
|
|
1057
|
+
${list}`;
|
|
1058
|
+
}
|
|
1068
1059
|
return {
|
|
1069
1060
|
content: [{
|
|
1070
1061
|
type: "text",
|
|
@@ -1073,7 +1064,7 @@ function registerStatusTool(server, client) {
|
|
|
1073
1064
|
`IP: ${ipLine}`,
|
|
1074
1065
|
`Port: ${port}`,
|
|
1075
1066
|
``,
|
|
1076
|
-
|
|
1067
|
+
peersSection
|
|
1077
1068
|
].join("\n")
|
|
1078
1069
|
}]
|
|
1079
1070
|
};
|
|
@@ -1091,7 +1082,6 @@ function createMcpServer(options) {
|
|
|
1091
1082
|
registerStatusTool(server, client);
|
|
1092
1083
|
registerAskTool(server, client);
|
|
1093
1084
|
registerReplyTool(server, client);
|
|
1094
|
-
registerPeersTool(server, client);
|
|
1095
1085
|
registerHistoryTool(server, client);
|
|
1096
1086
|
return server;
|
|
1097
1087
|
}
|
|
@@ -1112,7 +1102,7 @@ async function main() {
|
|
|
1112
1102
|
console.error("--name is required");
|
|
1113
1103
|
process.exit(1);
|
|
1114
1104
|
}
|
|
1115
|
-
const node = new
|
|
1105
|
+
const node = new MeshNode();
|
|
1116
1106
|
const mcpReady = startMcpServer({ client: node });
|
|
1117
1107
|
node.join(name, name).catch((err) => {
|
|
1118
1108
|
console.error(`[mcp-main] Failed to start on port 12345: ${err.message}`);
|