@teamclaws/teamclaw 2026.3.21

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.
@@ -0,0 +1,101 @@
1
+ import type { PluginLogger } from "../api.js";
2
+ import type { DiscoveryResult } from "./types.js";
3
+ import { MDNS_TYPE } from "./protocol.js";
4
+
5
+ export class MDnsAdvertiser {
6
+ private service: InstanceType<typeof import("bonjour-service").default> | null = null;
7
+ private advertisement: ReturnType<InstanceType<typeof import("bonjour-service").default>["publish"]> | null = null;
8
+ private logger: PluginLogger;
9
+
10
+ constructor(logger: PluginLogger) {
11
+ this.logger = logger;
12
+ }
13
+
14
+ async start(port: number, teamName: string): Promise<void> {
15
+ try {
16
+ const Bonjour = (await import("bonjour-service")).default;
17
+ this.service = new Bonjour();
18
+ this.advertisement = this.service.publish({
19
+ name: `teamclaw-${teamName}-controller`,
20
+ type: MDNS_TYPE,
21
+ port,
22
+ txt: { teamName },
23
+ });
24
+ this.logger.info(`mDNS: advertising teamclaw controller on port ${port}`);
25
+ } catch (err) {
26
+ this.logger.warn(`mDNS: failed to start advertising: ${err instanceof Error ? err.message : String(err)}`);
27
+ }
28
+ }
29
+
30
+ stop(): void {
31
+ if (this.advertisement) {
32
+ this.advertisement.stop();
33
+ this.advertisement = null;
34
+ }
35
+ if (this.service) {
36
+ this.service.destroy();
37
+ this.service = null;
38
+ }
39
+ }
40
+ }
41
+
42
+ export class MDnsBrowser {
43
+ private logger: PluginLogger;
44
+
45
+ constructor(logger: PluginLogger) {
46
+ this.logger = logger;
47
+ }
48
+
49
+ async browse(teamName?: string, timeoutMs = 5000): Promise<DiscoveryResult[]> {
50
+ const results: DiscoveryResult[] = [];
51
+ try {
52
+ const Bonjour = (await import("bonjour-service")).default;
53
+ const bonjour = new Bonjour();
54
+
55
+ await new Promise<void>((resolve) => {
56
+ const browser = bonjour.find({
57
+ type: MDNS_TYPE,
58
+ ...(teamName ? { txt: { teamName } } : {}),
59
+ });
60
+
61
+ const timer = setTimeout(() => {
62
+ browser.stop();
63
+ bonjour.destroy();
64
+ resolve();
65
+ }, timeoutMs);
66
+
67
+ browser.on("up", (service) => {
68
+ const txtRecord = service.txt as Record<string, string> | undefined;
69
+ const svcTeamName = txtRecord?.teamName ?? "default";
70
+ const host = Array.isArray(service.addresses) && service.addresses.length > 0
71
+ ? service.addresses[0]
72
+ : (service.host ?? "localhost");
73
+ const port = typeof service.port === "number" ? service.port : 9527;
74
+
75
+ results.push({
76
+ name: service.name ?? "unknown",
77
+ host,
78
+ port,
79
+ teamName: svcTeamName,
80
+ });
81
+
82
+ this.logger.info(`mDNS: found controller at ${host}:${port} (team: ${svcTeamName})`);
83
+
84
+ // Found at least one result, stop browsing
85
+ clearTimeout(timer);
86
+ browser.stop();
87
+ bonjour.destroy();
88
+ resolve();
89
+ });
90
+
91
+ browser.on("error", (err) => {
92
+ this.logger.warn(`mDNS: browse error: ${String(err)}`);
93
+ });
94
+ });
95
+ } catch (err) {
96
+ this.logger.warn(`mDNS: failed to browse: ${err instanceof Error ? err.message : String(err)}`);
97
+ }
98
+
99
+ return results;
100
+ }
101
+ }