@dolusoft/claude-collab 1.11.3 → 1.11.5
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 +106 -21
- package/dist/cli.js.map +1 -1
- package/dist/mcp-main.js +106 -21
- package/dist/mcp-main.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -59,7 +59,7 @@ public class ConInject {
|
|
|
59
59
|
string lpFileName, uint dwDesiredAccess, uint dwShareMode,
|
|
60
60
|
IntPtr lpSecurityAttributes, uint dwCreationDisposition,
|
|
61
61
|
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
|
|
62
|
-
[DllImport("kernel32.dll")] public static extern bool WriteConsoleInput(
|
|
62
|
+
[DllImport("kernel32.dll", CharSet=CharSet.Unicode)] public static extern bool WriteConsoleInput(
|
|
63
63
|
IntPtr hIn, INPUT_RECORD[] buf, uint len, out uint written);
|
|
64
64
|
[DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr h);
|
|
65
65
|
|
|
@@ -270,8 +270,10 @@ var FIXED_PORT = 12345;
|
|
|
270
270
|
var MeshNode = class {
|
|
271
271
|
server = null;
|
|
272
272
|
myName = "";
|
|
273
|
+
myProfile = "";
|
|
273
274
|
running = false;
|
|
274
275
|
firewallOpened = false;
|
|
276
|
+
cachedLocalIps = [];
|
|
275
277
|
// One connection per peer (inbound or outbound — whichever was established first)
|
|
276
278
|
peerConnections = /* @__PURE__ */ new Map();
|
|
277
279
|
// Reverse map: ws → peer name (only for registered connections)
|
|
@@ -284,6 +286,8 @@ var MeshNode = class {
|
|
|
284
286
|
receivedAnswers = /* @__PURE__ */ new Map();
|
|
285
287
|
questionToSender = /* @__PURE__ */ new Map();
|
|
286
288
|
pendingHandlers = /* @__PURE__ */ new Set();
|
|
289
|
+
// Peer profiles: peerName → profile text
|
|
290
|
+
peerProfiles = /* @__PURE__ */ new Map();
|
|
287
291
|
// Push-based answer resolution: questionId → resolve callback
|
|
288
292
|
answerWaiters = /* @__PURE__ */ new Map();
|
|
289
293
|
// Answers queued for offline peers: peerName → AnswerMsg[] (delivered on reconnect)
|
|
@@ -302,6 +306,7 @@ var MeshNode = class {
|
|
|
302
306
|
}
|
|
303
307
|
async join(name, displayName) {
|
|
304
308
|
this.myName = name;
|
|
309
|
+
this.cachedLocalIps = this.scanLocalIps();
|
|
305
310
|
await this.startServer();
|
|
306
311
|
return {
|
|
307
312
|
memberId: v4(),
|
|
@@ -325,7 +330,7 @@ var MeshNode = class {
|
|
|
325
330
|
reject(new Error(`Connection to ${ip}:${FIXED_PORT} timed out`));
|
|
326
331
|
}, 1e4);
|
|
327
332
|
ws.on("open", () => {
|
|
328
|
-
this.sendToWs(ws, { type: "HELLO", name: this.myName });
|
|
333
|
+
this.sendToWs(ws, { type: "HELLO", name: this.myName, profile: this.myProfile });
|
|
329
334
|
});
|
|
330
335
|
ws.on("message", (data) => {
|
|
331
336
|
try {
|
|
@@ -337,6 +342,7 @@ var MeshNode = class {
|
|
|
337
342
|
resolve(msg.name);
|
|
338
343
|
return;
|
|
339
344
|
}
|
|
345
|
+
if (msg.profile) this.peerProfiles.set(msg.name, msg.profile);
|
|
340
346
|
this.registerPeer(msg.name, ip, ws);
|
|
341
347
|
this.afterHandshake(msg.name, ws);
|
|
342
348
|
resolve(msg.name);
|
|
@@ -378,7 +384,10 @@ var MeshNode = class {
|
|
|
378
384
|
}
|
|
379
385
|
waitForAnswer(questionId, timeoutMs) {
|
|
380
386
|
const cached = this.receivedAnswers.get(questionId);
|
|
381
|
-
if (cached)
|
|
387
|
+
if (cached) {
|
|
388
|
+
this.receivedAnswers.delete(questionId);
|
|
389
|
+
return Promise.resolve(this.formatAnswer(questionId, cached));
|
|
390
|
+
}
|
|
382
391
|
return new Promise((resolve) => {
|
|
383
392
|
const timeout = setTimeout(() => {
|
|
384
393
|
this.answerWaiters.delete(questionId);
|
|
@@ -392,7 +401,9 @@ var MeshNode = class {
|
|
|
392
401
|
}
|
|
393
402
|
async checkAnswer(questionId) {
|
|
394
403
|
const cached = this.receivedAnswers.get(questionId);
|
|
395
|
-
|
|
404
|
+
if (!cached) return null;
|
|
405
|
+
this.receivedAnswers.delete(questionId);
|
|
406
|
+
return this.formatAnswer(questionId, cached);
|
|
396
407
|
}
|
|
397
408
|
formatAnswer(questionId, cached) {
|
|
398
409
|
return {
|
|
@@ -451,10 +462,15 @@ var MeshNode = class {
|
|
|
451
462
|
teamName: this.myName,
|
|
452
463
|
port: FIXED_PORT,
|
|
453
464
|
connectedPeers: [...this.peerConnections.keys()],
|
|
454
|
-
peerIPs: Object.fromEntries(this.peerIPs)
|
|
465
|
+
peerIPs: Object.fromEntries(this.peerIPs),
|
|
466
|
+
myProfile: this.myProfile,
|
|
467
|
+
peerProfiles: Object.fromEntries(this.peerProfiles)
|
|
455
468
|
};
|
|
456
469
|
}
|
|
457
470
|
getLocalIps() {
|
|
471
|
+
return this.cachedLocalIps;
|
|
472
|
+
}
|
|
473
|
+
scanLocalIps() {
|
|
458
474
|
const result = [];
|
|
459
475
|
const interfaces = os.networkInterfaces();
|
|
460
476
|
for (const iface of Object.values(interfaces)) {
|
|
@@ -465,6 +481,12 @@ var MeshNode = class {
|
|
|
465
481
|
}
|
|
466
482
|
return result;
|
|
467
483
|
}
|
|
484
|
+
async setProfile(profile) {
|
|
485
|
+
this.myProfile = profile;
|
|
486
|
+
for (const ws of this.peerConnections.values()) {
|
|
487
|
+
this.sendToWs(ws, { type: "PROFILE_UPDATE", name: this.myName, profile });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
468
490
|
async disconnect() {
|
|
469
491
|
for (const ws of this.peerConnections.values()) ws.close();
|
|
470
492
|
this.peerConnections.clear();
|
|
@@ -540,7 +562,12 @@ var MeshNode = class {
|
|
|
540
562
|
// ---------------------------------------------------------------------------
|
|
541
563
|
/** Called once handshake completes — exchange peer lists and announce to existing peers. */
|
|
542
564
|
afterHandshake(newPeerName, ws) {
|
|
543
|
-
const listForNew = [...this.peerConnections.keys()].filter((n) => n !== newPeerName).map((n) =>
|
|
565
|
+
const listForNew = [...this.peerConnections.keys()].filter((n) => n !== newPeerName).map((n) => {
|
|
566
|
+
const entry = { name: n, ip: this.peerIPs.get(n) ?? "" };
|
|
567
|
+
const profile = this.peerProfiles.get(n);
|
|
568
|
+
if (profile) entry.profile = profile;
|
|
569
|
+
return entry;
|
|
570
|
+
}).filter((p) => p.ip !== "");
|
|
544
571
|
this.sendToWs(ws, { type: "PEER_LIST", peers: listForNew });
|
|
545
572
|
const newIp = this.peerIPs.get(newPeerName);
|
|
546
573
|
if (newIp) {
|
|
@@ -566,7 +593,7 @@ var MeshNode = class {
|
|
|
566
593
|
this.connectingPeers.add(name);
|
|
567
594
|
const ws = new WebSocket(`ws://${ip}:${FIXED_PORT}`);
|
|
568
595
|
ws.on("open", () => {
|
|
569
|
-
this.sendToWs(ws, { type: "HELLO", name: this.myName });
|
|
596
|
+
this.sendToWs(ws, { type: "HELLO", name: this.myName, profile: this.myProfile });
|
|
570
597
|
});
|
|
571
598
|
ws.on("message", (data) => {
|
|
572
599
|
try {
|
|
@@ -600,8 +627,9 @@ var MeshNode = class {
|
|
|
600
627
|
ws.terminate();
|
|
601
628
|
return;
|
|
602
629
|
}
|
|
630
|
+
if (msg.profile) this.peerProfiles.set(msg.name, msg.profile);
|
|
603
631
|
this.registerPeer(msg.name, remoteIp, ws);
|
|
604
|
-
this.sendToWs(ws, { type: "HELLO_ACK", name: this.myName });
|
|
632
|
+
this.sendToWs(ws, { type: "HELLO_ACK", name: this.myName, profile: this.myProfile });
|
|
605
633
|
console.error(`[mesh] peer joined (inbound): ${msg.name}`);
|
|
606
634
|
this.afterHandshake(msg.name, ws);
|
|
607
635
|
return;
|
|
@@ -614,6 +642,7 @@ var MeshNode = class {
|
|
|
614
642
|
switch (msg.type) {
|
|
615
643
|
case "HELLO_ACK":
|
|
616
644
|
if (!this.peerConnections.has(msg.name)) {
|
|
645
|
+
if (msg.profile) this.peerProfiles.set(msg.name, msg.profile);
|
|
617
646
|
this.peerConnections.set(msg.name, ws);
|
|
618
647
|
this.wsToName.set(ws, msg.name);
|
|
619
648
|
this.connectingPeers.delete(msg.name);
|
|
@@ -627,6 +656,7 @@ var MeshNode = class {
|
|
|
627
656
|
if (peer.name !== this.myName && !this.peerConnections.has(peer.name)) {
|
|
628
657
|
console.error(`[mesh] connecting to ${peer.name} @ ${peer.ip} via PEER_LIST`);
|
|
629
658
|
this.peerIPs.set(peer.name, peer.ip);
|
|
659
|
+
if (peer.profile) this.peerProfiles.set(peer.name, peer.profile);
|
|
630
660
|
this.connectMeshPeer(peer.name, peer.ip);
|
|
631
661
|
}
|
|
632
662
|
}
|
|
@@ -649,14 +679,18 @@ var MeshNode = class {
|
|
|
649
679
|
answeredAt: msg.answeredAt,
|
|
650
680
|
fromName: msg.from
|
|
651
681
|
};
|
|
652
|
-
this.receivedAnswers.set(msg.questionId, record);
|
|
653
682
|
const waiter = this.answerWaiters.get(msg.questionId);
|
|
654
683
|
if (waiter) {
|
|
655
684
|
this.answerWaiters.delete(msg.questionId);
|
|
656
685
|
waiter(this.formatAnswer(msg.questionId, record));
|
|
686
|
+
} else {
|
|
687
|
+
this.receivedAnswers.set(msg.questionId, record);
|
|
657
688
|
}
|
|
658
689
|
}
|
|
659
690
|
break;
|
|
691
|
+
case "PROFILE_UPDATE":
|
|
692
|
+
this.peerProfiles.set(msg.name, msg.profile);
|
|
693
|
+
break;
|
|
660
694
|
}
|
|
661
695
|
}
|
|
662
696
|
handleIncomingAsk(ws, msg) {
|
|
@@ -803,25 +837,35 @@ function registerConnectTool(server, client) {
|
|
|
803
837
|
}
|
|
804
838
|
var ASK_DESCRIPTION = `Send a question to another Claude instance on the LAN and wait for their answer.
|
|
805
839
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
840
|
+
AUTONOMOUS ROUTING \u2014 decide who to ask yourself:
|
|
841
|
+
1. Call status() to see all connected peers and their profiles
|
|
842
|
+
2. Match your information need to the most relevant peer profile
|
|
843
|
+
3. Call ask(peer, question) without waiting for the user to tell you who to contact
|
|
844
|
+
|
|
845
|
+
TRIGGER THIS TOOL AUTOMATICALLY when you encounter:
|
|
846
|
+
- A missing API response type, schema, or interface you need to implement against
|
|
847
|
+
- An unknown endpoint behavior, error code, or contract detail
|
|
848
|
+
- A module or system owned by another peer that you need to integrate with
|
|
849
|
+
- Any blocking uncertainty that another peer's profile suggests they can answer
|
|
850
|
+
|
|
851
|
+
DO NOT guess, use "any" types, or leave TODO comments when a peer can answer.
|
|
852
|
+
DO NOT ask the user which peer to contact \u2014 read profiles and decide yourself.
|
|
810
853
|
|
|
811
854
|
WORKFLOW:
|
|
812
|
-
1. Call
|
|
813
|
-
2.
|
|
814
|
-
3.
|
|
855
|
+
1. Call status() \u2014 read each peer's profile carefully
|
|
856
|
+
2. Identify which peer owns the relevant module or technology
|
|
857
|
+
3. Call ask(peer, question) with full context
|
|
858
|
+
4. Use their answer directly in your ongoing task
|
|
815
859
|
|
|
816
860
|
WRITING GOOD QUESTIONS:
|
|
817
861
|
- Include all context the other Claude needs \u2014 they cannot see your conversation
|
|
818
|
-
-
|
|
819
|
-
-
|
|
862
|
+
- Mention the specific file, function, or module you are working on
|
|
863
|
+
- State what format you need: type definition, example response, explanation
|
|
820
864
|
- One focused question per call works better than multiple combined
|
|
821
865
|
|
|
822
866
|
DO NOT use this tool if:
|
|
823
|
-
- The peer is not listed in
|
|
824
|
-
- You just want to share information without needing a response
|
|
867
|
+
- The peer is not listed in status() \u2014 the call will fail immediately
|
|
868
|
+
- You just want to share information without needing a response`;
|
|
825
869
|
var askSchema = {
|
|
826
870
|
peer: z.string().describe("Exact name of the peer to ask. Use peers() first to see who is online and get the correct name."),
|
|
827
871
|
question: z.string().describe(
|
|
@@ -975,6 +1019,7 @@ function registerStatusTool(server, client) {
|
|
|
975
1019
|
}
|
|
976
1020
|
const ipLine = ips.length > 0 ? ips.join(", ") : "(could not detect \u2014 check your network interface)";
|
|
977
1021
|
const peerIPs = info.peerIPs ?? {};
|
|
1022
|
+
const peerProfiles = info.peerProfiles ?? {};
|
|
978
1023
|
const connected = info.connectedPeers;
|
|
979
1024
|
let peersSection;
|
|
980
1025
|
if (connected.length === 0) {
|
|
@@ -982,7 +1027,9 @@ function registerStatusTool(server, client) {
|
|
|
982
1027
|
} else {
|
|
983
1028
|
const list = connected.map((n) => {
|
|
984
1029
|
const ip = peerIPs[n] ? ` (${peerIPs[n]})` : "";
|
|
985
|
-
|
|
1030
|
+
const profile = peerProfiles[n] ? `
|
|
1031
|
+
${peerProfiles[n]}` : "";
|
|
1032
|
+
return ` \u2022 ${n}${ip}${profile}`;
|
|
986
1033
|
}).join("\n");
|
|
987
1034
|
peersSection = `Connected peers (${connected.length}):
|
|
988
1035
|
${list}`;
|
|
@@ -1001,6 +1048,43 @@ ${list}`;
|
|
|
1001
1048
|
};
|
|
1002
1049
|
});
|
|
1003
1050
|
}
|
|
1051
|
+
var SET_PROFILE_DESCRIPTION = `Set your profile so other peers know who you are and what you work on.
|
|
1052
|
+
|
|
1053
|
+
Your profile is broadcast to all connected peers immediately and shared
|
|
1054
|
+
with any peer that connects in the future. Other Claude instances use
|
|
1055
|
+
your profile to decide who to ask when they need specific information.
|
|
1056
|
+
|
|
1057
|
+
WHAT TO INCLUDE:
|
|
1058
|
+
- Your role (backend, frontend, devops, etc.)
|
|
1059
|
+
- The modules or systems you are responsible for
|
|
1060
|
+
- Technologies you work with
|
|
1061
|
+
|
|
1062
|
+
EXAMPLES:
|
|
1063
|
+
- "Backend developer. Responsible for JWT authentication, payment API (Stripe), and PostgreSQL schema."
|
|
1064
|
+
- "Frontend developer. Works on React components, Next.js routing, and the checkout flow."
|
|
1065
|
+
- "DevOps. Manages CI/CD pipelines, Docker images, and AWS infrastructure."
|
|
1066
|
+
|
|
1067
|
+
Call this once when you start working. Update it if your focus changes.`;
|
|
1068
|
+
var setProfileSchema = {
|
|
1069
|
+
profile: z.string().min(1).describe("A short description of your role and responsibilities. 1-3 sentences.")
|
|
1070
|
+
};
|
|
1071
|
+
function registerSetProfileTool(server, client) {
|
|
1072
|
+
server.tool("set_profile", SET_PROFILE_DESCRIPTION, setProfileSchema, async (args) => {
|
|
1073
|
+
await client.setProfile(args.profile);
|
|
1074
|
+
const info = client.getInfo();
|
|
1075
|
+
const peerCount = info.connectedPeers.length;
|
|
1076
|
+
return {
|
|
1077
|
+
content: [{
|
|
1078
|
+
type: "text",
|
|
1079
|
+
text: peerCount > 0 ? `Profile set and broadcast to ${peerCount} peer(s).
|
|
1080
|
+
|
|
1081
|
+
"${args.profile}"` : `Profile saved. It will be shared automatically when peers connect.
|
|
1082
|
+
|
|
1083
|
+
"${args.profile}"`
|
|
1084
|
+
}]
|
|
1085
|
+
};
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1004
1088
|
|
|
1005
1089
|
// src/presentation/mcp/server.ts
|
|
1006
1090
|
function createMcpServer(options) {
|
|
@@ -1013,6 +1097,7 @@ function createMcpServer(options) {
|
|
|
1013
1097
|
registerStatusTool(server, client);
|
|
1014
1098
|
registerAskTool(server, client);
|
|
1015
1099
|
registerReplyTool(server, client);
|
|
1100
|
+
registerSetProfileTool(server, client);
|
|
1016
1101
|
return server;
|
|
1017
1102
|
}
|
|
1018
1103
|
async function startMcpServer(options) {
|