@dolusoft/claude-collab 1.8.1 → 1.8.3

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 CHANGED
@@ -704,10 +704,17 @@ var HubManager = class {
704
704
  server.start(port);
705
705
  this.hubServer = server;
706
706
  this.currentPort = port;
707
- await addFirewallRule(port);
708
707
  const advertiser = new MdnsAdvertiser();
709
708
  advertiser.start(port);
710
709
  this.advertiser = advertiser;
710
+ let firewallAdded = false;
711
+ try {
712
+ await addFirewallRule(port);
713
+ firewallAdded = true;
714
+ } catch (err) {
715
+ console.error("[hub-manager] firewall rule failed:", err);
716
+ }
717
+ return { firewallAdded };
711
718
  }
712
719
  async stop() {
713
720
  if (!this.isRunning) throw new Error("Hub is not running");
@@ -719,34 +726,51 @@ var HubManager = class {
719
726
  this.hubServer.stop();
720
727
  this.hubServer = null;
721
728
  this.currentPort = null;
722
- await removeFirewallRule(port);
729
+ let firewallRemoved = false;
730
+ try {
731
+ await removeFirewallRule(port);
732
+ firewallRemoved = true;
733
+ } catch (err) {
734
+ console.error("[hub-manager] firewall rule removal failed:", err);
735
+ }
736
+ return { firewallRemoved };
723
737
  }
724
738
  };
725
- function runElevated(netshArgs) {
739
+ function runElevated(argArray) {
740
+ const argList = argArray.map((a) => `"${a}"`).join(",");
741
+ const psCommand = `Start-Process -FilePath "netsh" -ArgumentList @(${argList}) -Verb RunAs -Wait`;
726
742
  return new Promise((resolve, reject) => {
727
- const ps = spawn("powershell", [
728
- "-NoProfile",
729
- "-Command",
730
- `Start-Process -FilePath "netsh" -ArgumentList "${netshArgs}" -Verb RunAs -Wait`
731
- ]);
743
+ const ps = spawn("powershell", ["-NoProfile", "-Command", psCommand]);
732
744
  ps.on("close", (code) => {
733
745
  if (code === 0) resolve();
734
- else reject(new Error(`Firewall command failed (exit code ${code}). User may have cancelled the UAC prompt.`));
746
+ else reject(new Error(`Firewall UAC prompt was cancelled or denied (exit code ${code}).`));
735
747
  });
736
748
  ps.on("error", (err) => {
737
- reject(new Error(`Failed to launch PowerShell for firewall elevation: ${err.message}`));
749
+ reject(new Error(`Failed to launch PowerShell: ${err.message}`));
738
750
  });
739
751
  });
740
752
  }
741
753
  async function addFirewallRule(port) {
742
- await runElevated(
743
- `advfirewall firewall add rule name="claude-collab-${port}" protocol=TCP dir=in localport=${port} action=allow`
744
- );
754
+ await runElevated([
755
+ "advfirewall",
756
+ "firewall",
757
+ "add",
758
+ "rule",
759
+ `name=claude-collab-${port}`,
760
+ "protocol=TCP",
761
+ "dir=in",
762
+ `localport=${port}`,
763
+ "action=allow"
764
+ ]);
745
765
  }
746
766
  async function removeFirewallRule(port) {
747
- await runElevated(
748
- `advfirewall firewall delete rule name="claude-collab-${port}"`
749
- );
767
+ await runElevated([
768
+ "advfirewall",
769
+ "firewall",
770
+ "delete",
771
+ "rule",
772
+ `name=claude-collab-${port}`
773
+ ]);
750
774
  }
751
775
  var askSchema = {
752
776
  peer: z.string().describe('Name of the peer to ask (e.g., "alice", "backend")'),
@@ -923,15 +947,14 @@ function registerStartHubTool(server, client, hubManager) {
923
947
  }]
924
948
  };
925
949
  }
950
+ let firewallAdded = false;
926
951
  try {
927
- await hubManager.start(port);
952
+ const result = await hubManager.start(port);
953
+ firewallAdded = result.firewallAdded;
928
954
  } catch (err) {
929
955
  const msg = err instanceof Error ? err.message : String(err);
930
956
  return {
931
- content: [{
932
- type: "text",
933
- text: `Failed to start hub: ${msg}`
934
- }]
957
+ content: [{ type: "text", text: `Failed to start hub: ${msg}` }]
935
958
  };
936
959
  }
937
960
  try {
@@ -939,22 +962,17 @@ function registerStartHubTool(server, client, hubManager) {
939
962
  } catch (err) {
940
963
  const msg = err instanceof Error ? err.message : String(err);
941
964
  return {
942
- content: [{
943
- type: "text",
944
- text: `Hub started on port ${port}, but failed to self-connect: ${msg}`
945
- }]
965
+ content: [{ type: "text", text: `Hub started on port ${port}, but failed to self-connect: ${msg}` }]
946
966
  };
947
967
  }
968
+ const lines = [
969
+ `Hub started on port ${port}.`,
970
+ firewallAdded ? `Firewall rule added (claude-collab-${port}) \u2014 LAN peers can connect.` : `WARNING: Firewall rule could not be added (UAC was cancelled or denied). Peers on other machines may be blocked by Windows Firewall. Run start_hub again and accept the UAC prompt to fix this.`,
971
+ `Others on the LAN will auto-discover and connect via mDNS.`,
972
+ `Use stop_hub when you are done.`
973
+ ];
948
974
  return {
949
- content: [{
950
- type: "text",
951
- text: [
952
- `Hub started on port ${port}.`,
953
- `Firewall rule added (claude-collab-${port}).`,
954
- `Others on the LAN will auto-discover and connect \u2014 no IP sharing needed.`,
955
- `Use stop_hub when you are done to close the hub and remove the firewall rule.`
956
- ].join("\n")
957
- }]
975
+ content: [{ type: "text", text: lines.join("\n") }]
958
976
  };
959
977
  }
960
978
  );
@@ -976,26 +994,23 @@ function registerStopHubTool(server, hubManager) {
976
994
  };
977
995
  }
978
996
  const port = hubManager.port;
997
+ let firewallRemoved = false;
979
998
  try {
980
- await hubManager.stop();
999
+ const result = await hubManager.stop();
1000
+ firewallRemoved = result.firewallRemoved;
981
1001
  } catch (err) {
982
1002
  const msg = err instanceof Error ? err.message : String(err);
983
1003
  return {
984
- content: [{
985
- type: "text",
986
- text: `Failed to stop hub: ${msg}`
987
- }]
1004
+ content: [{ type: "text", text: `Failed to stop hub: ${msg}` }]
988
1005
  };
989
1006
  }
1007
+ const lines = [
1008
+ `Hub stopped (was on port ${port}).`,
1009
+ firewallRemoved ? `Firewall rule removed (claude-collab-${port}).` : `WARNING: Firewall rule could not be removed (UAC was cancelled). Remove it manually: netsh advfirewall firewall delete rule name="claude-collab-${port}"`,
1010
+ `All peers have been disconnected.`
1011
+ ];
990
1012
  return {
991
- content: [{
992
- type: "text",
993
- text: [
994
- `Hub stopped (was on port ${port}).`,
995
- `Firewall rule removed (claude-collab-${port}).`,
996
- `All peers have been disconnected.`
997
- ].join("\n")
998
- }]
1013
+ content: [{ type: "text", text: lines.join("\n") }]
999
1014
  };
1000
1015
  }
1001
1016
  );
@@ -1021,6 +1036,37 @@ async function startMcpServer(options) {
1021
1036
  const transport = new StdioServerTransport();
1022
1037
  await server.connect(transport);
1023
1038
  }
1039
+ var SERVICE_TYPE2 = "claude-collab";
1040
+ function discoverHub(timeoutMs = 5e3) {
1041
+ return new Promise((resolve) => {
1042
+ const bonjour = new Bonjour();
1043
+ const browser = bonjour.find({ type: SERVICE_TYPE2 });
1044
+ let settled = false;
1045
+ const finish = (result) => {
1046
+ if (settled) return;
1047
+ settled = true;
1048
+ clearTimeout(timer);
1049
+ browser.stop();
1050
+ bonjour.destroy();
1051
+ resolve(result);
1052
+ };
1053
+ const timer = setTimeout(() => finish(null), timeoutMs);
1054
+ browser.on("up", (svc) => {
1055
+ finish({ host: svc.host, port: svc.port });
1056
+ });
1057
+ });
1058
+ }
1059
+ function watchForHub(onFound) {
1060
+ const bonjour = new Bonjour();
1061
+ const browser = bonjour.find({ type: SERVICE_TYPE2 });
1062
+ browser.on("up", (svc) => {
1063
+ onFound({ host: svc.host, port: svc.port });
1064
+ });
1065
+ return () => {
1066
+ browser.stop();
1067
+ bonjour.destroy();
1068
+ };
1069
+ }
1024
1070
 
1025
1071
  // src/cli.ts
1026
1072
  var program = new Command();
@@ -1039,19 +1085,42 @@ program.name("claude-collab").description("P2P collaboration between Claude Code
1039
1085
  });
1040
1086
  } else {
1041
1087
  if (!options.name) {
1042
- console.error("--name is required in client mode");
1088
+ console.error("--name is required");
1043
1089
  process.exit(1);
1044
1090
  }
1045
- if (!options.server) {
1046
- console.error("--server is required in client mode (e.g. --server 192.168.1.5:9999)");
1047
- process.exit(1);
1048
- }
1049
- const url = options.server.startsWith("ws") ? options.server : `ws://${options.server}`;
1050
1091
  const client = new HubClient();
1051
1092
  const hubManager = new HubManager();
1052
- client.setServerUrl(url);
1053
- await client.join(options.name, options.name);
1054
- await startMcpServer({ client, hubManager });
1093
+ if (options.server) {
1094
+ const url = options.server.startsWith("ws") ? options.server : `ws://${options.server}`;
1095
+ client.setServerUrl(url);
1096
+ await client.join(options.name, options.name);
1097
+ } else {
1098
+ await client.join(options.name, options.name);
1099
+ }
1100
+ const mcpReady = startMcpServer({ client, hubManager });
1101
+ if (!options.server) {
1102
+ discoverHub(5e3).then(async (hub) => {
1103
+ if (!hub || client.isConnected) return;
1104
+ try {
1105
+ await client.connectToHub(`ws://${hub.host}:${hub.port}`);
1106
+ } catch {
1107
+ }
1108
+ });
1109
+ const stopWatch = watchForHub(async (hub) => {
1110
+ if (client.isConnected) {
1111
+ stopWatch();
1112
+ return;
1113
+ }
1114
+ try {
1115
+ await client.connectToHub(`ws://${hub.host}:${hub.port}`);
1116
+ stopWatch();
1117
+ } catch (err) {
1118
+ const msg = err instanceof Error ? err.message : String(err);
1119
+ console.error(`[cli] auto-connect failed: ${msg}`);
1120
+ }
1121
+ });
1122
+ }
1123
+ await mcpReady;
1055
1124
  }
1056
1125
  });
1057
1126
  program.parse();