@dolusoft/claude-collab 1.9.6 → 1.9.7
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 +111 -70
- package/dist/cli.js.map +1 -1
- package/dist/mcp-main.js +111 -70
- package/dist/mcp-main.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
5
|
-
import
|
|
6
|
-
import { tmpdir
|
|
5
|
+
import dgram from 'dgram';
|
|
6
|
+
import os, { tmpdir } from 'os';
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
8
8
|
import { execFile, spawn } from 'child_process';
|
|
9
9
|
import { unlinkSync } from 'fs';
|
|
@@ -19,79 +19,121 @@ function serialize(msg) {
|
|
|
19
19
|
function parse(data) {
|
|
20
20
|
return JSON.parse(data);
|
|
21
21
|
}
|
|
22
|
-
var
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
for (const iface of ifaces ?? []) {
|
|
28
|
-
if (iface.family !== "IPv4" || iface.internal) continue;
|
|
29
|
-
const ip = iface.address.split(".").map(Number);
|
|
30
|
-
const mask = iface.netmask.split(".").map(Number);
|
|
31
|
-
const broadcast = ip.map((b, i) => b | ~mask[i] & 255).join(".");
|
|
32
|
-
addrs.add(broadcast);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return [...addrs];
|
|
36
|
-
}
|
|
37
|
-
var PeerBroadcaster = class {
|
|
22
|
+
var MULTICAST_ADDR = "239.255.42.42";
|
|
23
|
+
var MULTICAST_PORT = 11776;
|
|
24
|
+
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
25
|
+
var PEER_TIMEOUT_MS = 2e4;
|
|
26
|
+
var MulticastDiscovery = class extends EventEmitter {
|
|
38
27
|
socket = null;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
heartbeatTimer = null;
|
|
29
|
+
timeoutTimer = null;
|
|
30
|
+
peers = /* @__PURE__ */ new Map();
|
|
31
|
+
myName = "";
|
|
32
|
+
myWsPort = 0;
|
|
33
|
+
start(name, wsPort) {
|
|
34
|
+
this.myName = name;
|
|
35
|
+
this.myWsPort = wsPort;
|
|
36
|
+
const socket = dgram.createSocket({ type: "udp4", reuseAddr: true });
|
|
43
37
|
this.socket = socket;
|
|
44
38
|
socket.on("error", (err) => {
|
|
45
|
-
console.error("[
|
|
39
|
+
console.error("[multicast] socket error:", err.message);
|
|
46
40
|
});
|
|
47
|
-
socket.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
41
|
+
socket.on("message", (buf, rinfo) => {
|
|
42
|
+
try {
|
|
43
|
+
const msg = JSON.parse(buf.toString());
|
|
44
|
+
this.handleMessage(msg, rinfo.address);
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
socket.bind(MULTICAST_PORT, () => {
|
|
49
|
+
try {
|
|
50
|
+
socket.addMembership(MULTICAST_ADDR);
|
|
51
|
+
socket.setMulticastTTL(1);
|
|
52
|
+
socket.setMulticastLoopback(false);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error("[multicast] membership error:", err);
|
|
55
|
+
}
|
|
56
|
+
this.announce();
|
|
57
|
+
this.heartbeatTimer = setInterval(() => this.announce(), HEARTBEAT_INTERVAL_MS);
|
|
58
|
+
this.timeoutTimer = setInterval(() => this.checkTimeouts(), 5e3);
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
stop() {
|
|
63
|
-
if (this.
|
|
64
|
-
clearInterval(this.
|
|
65
|
-
this.
|
|
62
|
+
if (this.heartbeatTimer) {
|
|
63
|
+
clearInterval(this.heartbeatTimer);
|
|
64
|
+
this.heartbeatTimer = null;
|
|
65
|
+
}
|
|
66
|
+
if (this.timeoutTimer) {
|
|
67
|
+
clearInterval(this.timeoutTimer);
|
|
68
|
+
this.timeoutTimer = null;
|
|
66
69
|
}
|
|
67
70
|
if (this.socket) {
|
|
68
|
-
this.
|
|
71
|
+
this.sendMessage({ type: "LEAVE", name: this.myName });
|
|
72
|
+
try {
|
|
73
|
+
this.socket.dropMembership(MULTICAST_ADDR);
|
|
74
|
+
this.socket.close();
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
69
77
|
this.socket = null;
|
|
70
78
|
}
|
|
79
|
+
this.peers.clear();
|
|
71
80
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
socket.on("message", (msg, rinfo) => {
|
|
79
|
-
try {
|
|
80
|
-
const data = JSON.parse(msg.toString());
|
|
81
|
-
if (data.type === "claude-collab-peer" && typeof data.name === "string" && typeof data.port === "number") {
|
|
82
|
-
onFound({ name: data.name, host: rinfo.address, port: data.port });
|
|
81
|
+
resolveLocalIp() {
|
|
82
|
+
const interfaces = os.networkInterfaces();
|
|
83
|
+
for (const iface of Object.values(interfaces)) {
|
|
84
|
+
if (!iface) continue;
|
|
85
|
+
for (const addr of iface) {
|
|
86
|
+
if (addr.family === "IPv4" && !addr.internal) return addr.address;
|
|
83
87
|
}
|
|
84
|
-
} catch {
|
|
85
88
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
return "127.0.0.1";
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Private
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
announce() {
|
|
95
|
+
this.sendMessage({ type: "ANNOUNCE", name: this.myName, wsPort: this.myWsPort });
|
|
96
|
+
}
|
|
97
|
+
sendMessage(msg) {
|
|
98
|
+
if (!this.socket) return;
|
|
99
|
+
const buf = Buffer.from(JSON.stringify(msg));
|
|
100
|
+
this.socket.send(buf, MULTICAST_PORT, MULTICAST_ADDR, (err) => {
|
|
101
|
+
if (err) console.error("[multicast] send error:", err.message);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
handleMessage(msg, fromIp) {
|
|
105
|
+
if (msg.type === "ANNOUNCE") {
|
|
106
|
+
if (msg.name === this.myName) return;
|
|
107
|
+
const existing = this.peers.get(msg.name);
|
|
108
|
+
if (!existing) {
|
|
109
|
+
const peer = { name: msg.name, ip: fromIp, wsPort: msg.wsPort, lastSeen: Date.now() };
|
|
110
|
+
this.peers.set(msg.name, peer);
|
|
111
|
+
this.emit("peer-found", { name: peer.name, ip: peer.ip, wsPort: peer.wsPort });
|
|
112
|
+
console.error(`[multicast] discovered peer: ${msg.name} @ ${fromIp}:${msg.wsPort}`);
|
|
113
|
+
} else {
|
|
114
|
+
existing.lastSeen = Date.now();
|
|
115
|
+
existing.ip = fromIp;
|
|
116
|
+
existing.wsPort = msg.wsPort;
|
|
117
|
+
}
|
|
118
|
+
} else if (msg.type === "LEAVE") {
|
|
119
|
+
if (this.peers.has(msg.name)) {
|
|
120
|
+
this.peers.delete(msg.name);
|
|
121
|
+
this.emit("peer-lost", msg.name);
|
|
122
|
+
console.error(`[multicast] peer left: ${msg.name}`);
|
|
123
|
+
}
|
|
92
124
|
}
|
|
93
|
-
}
|
|
94
|
-
|
|
125
|
+
}
|
|
126
|
+
checkTimeouts() {
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
for (const [name, peer] of this.peers) {
|
|
129
|
+
if (now - peer.lastSeen > PEER_TIMEOUT_MS) {
|
|
130
|
+
this.peers.delete(name);
|
|
131
|
+
this.emit("peer-lost", name);
|
|
132
|
+
console.error(`[multicast] peer timed out: ${name}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
95
137
|
var CS_CONINJECT = `
|
|
96
138
|
using System;
|
|
97
139
|
using System.Collections.Generic;
|
|
@@ -333,8 +375,7 @@ var P2PNode = class {
|
|
|
333
375
|
answerWaiters = /* @__PURE__ */ new Map();
|
|
334
376
|
// Answers queued for offline peers: peerName → AnswerMsg (delivered on reconnect)
|
|
335
377
|
pendingOutboundAnswers = /* @__PURE__ */ new Map();
|
|
336
|
-
|
|
337
|
-
stopPeerWatcher = null;
|
|
378
|
+
discovery = null;
|
|
338
379
|
boundPort = 0;
|
|
339
380
|
// ---------------------------------------------------------------------------
|
|
340
381
|
// ICollabClient implementation
|
|
@@ -477,8 +518,8 @@ var P2PNode = class {
|
|
|
477
518
|
return entries.sort((a, b) => a.askedAt.localeCompare(b.askedAt));
|
|
478
519
|
}
|
|
479
520
|
async disconnect() {
|
|
480
|
-
this.
|
|
481
|
-
this.
|
|
521
|
+
this.discovery?.stop();
|
|
522
|
+
this.discovery = null;
|
|
482
523
|
for (const ws of this.peerConnections.values()) ws.close();
|
|
483
524
|
this.peerConnections.clear();
|
|
484
525
|
this.wsToName.clear();
|
|
@@ -549,14 +590,14 @@ var P2PNode = class {
|
|
|
549
590
|
// Private: discovery + outbound connections
|
|
550
591
|
// ---------------------------------------------------------------------------
|
|
551
592
|
startDiscovery() {
|
|
552
|
-
|
|
553
|
-
this.
|
|
554
|
-
|
|
555
|
-
if (peer.name === this.myName) return;
|
|
593
|
+
const discovery = new MulticastDiscovery();
|
|
594
|
+
this.discovery = discovery;
|
|
595
|
+
discovery.on("peer-found", (peer) => {
|
|
556
596
|
if (this.peerConnections.has(peer.name)) return;
|
|
557
597
|
if (this.connectingPeers.has(peer.name)) return;
|
|
558
|
-
this.connectToPeer(peer.name, peer.
|
|
598
|
+
this.connectToPeer(peer.name, peer.ip, peer.wsPort);
|
|
559
599
|
});
|
|
600
|
+
discovery.start(this.myName, this.boundPort);
|
|
560
601
|
}
|
|
561
602
|
connectToPeer(peerName, host, port) {
|
|
562
603
|
this.connectingPeers.add(peerName);
|
|
@@ -973,7 +1014,7 @@ async function addFirewallRule(port) {
|
|
|
973
1014
|
"name=claude-collab-discovery",
|
|
974
1015
|
"protocol=UDP",
|
|
975
1016
|
"dir=in",
|
|
976
|
-
"localport=
|
|
1017
|
+
"localport=11776",
|
|
977
1018
|
"action=allow"
|
|
978
1019
|
]);
|
|
979
1020
|
} catch {
|