create-metaclaw 3.3.0

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.
Files changed (92) hide show
  1. package/LICENSE +44 -0
  2. package/README.md +282 -0
  3. package/docs/assets/favicon.png +0 -0
  4. package/docs/assets/metaclaw-banner.svg +86 -0
  5. package/docs/assets/qis-logo.png +0 -0
  6. package/docs/assets/yz-favicon.png +0 -0
  7. package/docs/assets/yz-logo.png +0 -0
  8. package/docs/index.html +895 -0
  9. package/installer/assets/favicon.png +0 -0
  10. package/installer/auto-start.ts +330 -0
  11. package/installer/brand.ts +115 -0
  12. package/installer/core-scaffold.ts +448 -0
  13. package/installer/dashboard-generator.ts +657 -0
  14. package/installer/detect.ts +129 -0
  15. package/installer/index.ts +355 -0
  16. package/installer/module-loader.ts +412 -0
  17. package/installer/modules/boardroom/boardroom/client.ts.txt +201 -0
  18. package/installer/modules/boardroom/boardroom/db.ts.txt +322 -0
  19. package/installer/modules/boardroom/boardroom/meeting-agent.ts.txt +129 -0
  20. package/installer/modules/boardroom/boardroom/meeting-scheduler.ts.txt +194 -0
  21. package/installer/modules/boardroom/boardroom/server.ts.txt +473 -0
  22. package/installer/modules/boardroom/boardroom/start-boardroom.bat.txt +26 -0
  23. package/installer/modules/boardroom/boardroom/summons.ts.txt +76 -0
  24. package/installer/modules/boardroom/boardroom/turn-v2.ts.txt +172 -0
  25. package/installer/modules/boardroom/boardroom/turn.ts.txt +208 -0
  26. package/installer/modules/boardroom/boardroom/types.ts.txt +100 -0
  27. package/installer/modules/boardroom/metaclaw-module.json +35 -0
  28. package/installer/modules/boardroom/scripts/meeting-check.bat.txt +38 -0
  29. package/installer/modules/core/metaclaw-module.json +51 -0
  30. package/installer/modules/core/src/db.ts.txt +277 -0
  31. package/installer/modules/core/src/health-check.ts.txt +128 -0
  32. package/installer/modules/core/src/observability.ts.txt +20 -0
  33. package/installer/modules/core/src/safety.ts.txt +26 -0
  34. package/installer/modules/core/src/scan-capabilities.ts.txt +196 -0
  35. package/installer/modules/core/src/self-improve.ts.txt +48 -0
  36. package/installer/modules/core/src/self-update.ts.txt +345 -0
  37. package/installer/modules/core/src/sync-context.ts.txt +133 -0
  38. package/installer/modules/core/src/tasks.ts.txt +159 -0
  39. package/installer/modules/custom/metaclaw-module.json +15 -0
  40. package/installer/modules/custom/src/agent-custom.ts.txt +100 -0
  41. package/installer/modules/dashboard/metaclaw-module.json +23 -0
  42. package/installer/modules/dashboard/scripts/build-dashboard.cjs.txt +51 -0
  43. package/installer/modules/dashboard/src/update-dashboard.ts.txt +126 -0
  44. package/installer/modules/outreach/metaclaw-module.json +29 -0
  45. package/installer/modules/outreach/src/agent-outreach.ts.txt +193 -0
  46. package/installer/modules/outreach/src/inbox-agent.ts.txt +283 -0
  47. package/installer/modules/outreach/src/morning-report.ts.txt +124 -0
  48. package/installer/modules/research/metaclaw-module.json +15 -0
  49. package/installer/modules/research/src/agent-research.ts.txt +127 -0
  50. package/installer/modules/scheduler/metaclaw-module.json +27 -0
  51. package/installer/modules/scheduler/scripts/agent-cycle.bat.txt +85 -0
  52. package/installer/modules/scheduler/scripts/detect-session.bat.txt +41 -0
  53. package/installer/modules/scheduler/scripts/launch.bat.txt +120 -0
  54. package/installer/modules/scheduler/src/cron-manager.ts.txt +273 -0
  55. package/installer/modules/social/metaclaw-module.json +15 -0
  56. package/installer/modules/social/src/agent-social.ts.txt +110 -0
  57. package/installer/modules/support/metaclaw-module.json +15 -0
  58. package/installer/modules/support/src/agent-support.ts.txt +60 -0
  59. package/installer/modules/swarm/metaclaw-module.json +25 -0
  60. package/installer/modules/swarm/swarm/dht-client.ts.txt +376 -0
  61. package/installer/modules/swarm/swarm/relay-server.ts.txt +348 -0
  62. package/installer/modules/swarm/swarm/swarm-client.ts.txt +303 -0
  63. package/installer/modules/swarm/swarm/types.ts.txt +51 -0
  64. package/installer/modules/voice/metaclaw-module.json +16 -0
  65. package/installer/questionnaire.ts +277 -0
  66. package/installer/research.ts +258 -0
  67. package/installer/scaffold-from-config.ts +270 -0
  68. package/installer/task-generator.ts +324 -0
  69. package/installer/templates/agent-custom.ts.txt +100 -0
  70. package/installer/templates/agent-cycle.bat.txt +19 -0
  71. package/installer/templates/agent-outreach.ts.txt +193 -0
  72. package/installer/templates/agent-research.ts.txt +127 -0
  73. package/installer/templates/agent-social.ts.txt +110 -0
  74. package/installer/templates/agent-support.ts.txt +60 -0
  75. package/installer/templates/build-dashboard.cjs.txt +51 -0
  76. package/installer/templates/cron-manager.ts.txt +273 -0
  77. package/installer/templates/dashboard.html.txt +450 -0
  78. package/installer/templates/db.ts.txt +277 -0
  79. package/installer/templates/detect-session.bat.txt +41 -0
  80. package/installer/templates/health-check.ts.txt +128 -0
  81. package/installer/templates/inbox-agent.ts.txt +283 -0
  82. package/installer/templates/launch.bat.txt +120 -0
  83. package/installer/templates/morning-report.ts.txt +124 -0
  84. package/installer/templates/observability.ts.txt +20 -0
  85. package/installer/templates/safety.ts.txt +26 -0
  86. package/installer/templates/self-improve.ts.txt +48 -0
  87. package/installer/templates/self-update.ts.txt +345 -0
  88. package/installer/templates/state.json.txt +33 -0
  89. package/installer/templates/system-context.json.txt +33 -0
  90. package/installer/templates/update-dashboard.ts.txt +126 -0
  91. package/package.json +31 -0
  92. package/setup.bat +178 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * MetaClaw Swarm — DHT Client
3
+ *
4
+ * Decentralized bucket routing via Hyperswarm (Kademlia DHT).
5
+ * Every bucket = a DHT topic (SHA-256 of bucket path).
6
+ * Every node holds its own packets + serves them to querying peers.
7
+ * Querying node pulls ALL packets, tallies locally. Simple.
8
+ *
9
+ * Fallback: HTTP relay (configured in data/swarm-config.json)
10
+ */
11
+
12
+ import Hyperswarm from "hyperswarm";
13
+ import crypto from "crypto";
14
+ import fs from "fs";
15
+ import path from "path";
16
+
17
+ const RELAY_URL = "__RELAY_URL__";
18
+ const KEYPAIR_PATH = path.join(process.cwd(), "data", "dht-keypair.json");
19
+ const TOPICS_PATH = path.join(process.cwd(), "data", "dht-topics.json");
20
+
21
+ export interface DHTPacket {
22
+ bucket: string;
23
+ agent_id: string;
24
+ treatment?: string;
25
+ method?: string;
26
+ outcome?: string;
27
+ signal: "positive" | "negative" | "neutral";
28
+ confidence: number;
29
+ insight: string;
30
+ context: Record<string, any>;
31
+ metrics: Record<string, any>;
32
+ ts: number;
33
+ }
34
+
35
+ export class DHTClient {
36
+ private swarm: Hyperswarm | null = null;
37
+ private localPackets: Map<string, DHTPacket[]> = new Map(); // topic hex → my packets
38
+ private peerPackets: Map<string, DHTPacket[]> = new Map(); // topic hex → collected from peers
39
+ private connections: Map<string, Set<any>> = new Map(); // topic hex → active sockets
40
+ private agentId: string;
41
+ private ready: boolean = false;
42
+
43
+ constructor(agentId: string) {
44
+ this.agentId = agentId;
45
+ }
46
+
47
+ async start(): Promise<void> {
48
+ const keyPair = this.getOrCreateKeypair();
49
+ this.swarm = new Hyperswarm({ keyPair });
50
+
51
+ this.swarm.on("connection", (socket: any, info: any) => {
52
+ this.handleConnection(socket, info);
53
+ });
54
+
55
+ this.swarm.on("error", (err: Error) => {
56
+ console.error("[DHT] Error (non-fatal):", err.message);
57
+ });
58
+
59
+ // Rejoin previously announced topics
60
+ const savedTopics = this.loadTopics();
61
+ for (const topicHex of savedTopics) {
62
+ const topic = Buffer.from(topicHex, "hex");
63
+ this.swarm.join(topic, { server: true, client: true });
64
+ }
65
+
66
+ this.ready = true;
67
+ console.log(`[DHT] Online. Node: ${keyPair.publicKey.toString("hex").slice(0, 12)}...`);
68
+ }
69
+
70
+ async stop(): Promise<void> {
71
+ if (this.swarm) {
72
+ await this.swarm.destroy();
73
+ this.swarm = null;
74
+ }
75
+ this.ready = false;
76
+ }
77
+
78
+ // --- Core Operations ---
79
+
80
+ /**
81
+ * Join a bucket topic — announce that you have data for this problem
82
+ */
83
+ async joinBucket(bucketPath: string): Promise<string> {
84
+ if (!this.swarm) throw new Error("DHT not started");
85
+ const topic = this.hashBucket(bucketPath);
86
+ const topicHex = topic.toString("hex");
87
+
88
+ this.swarm.join(topic, { server: true, client: true });
89
+ this.saveTopicList(topicHex);
90
+
91
+ return topicHex;
92
+ }
93
+
94
+ /**
95
+ * Deposit your packet for a bucket — stores locally + pushes to connected peers + relay
96
+ */
97
+ async deposit(bucketPath: string, packet: DHTPacket): Promise<void> {
98
+ const topicHex = this.hashBucket(bucketPath).toString("hex");
99
+
100
+ // Store locally
101
+ if (!this.localPackets.has(topicHex)) this.localPackets.set(topicHex, []);
102
+ const existing = this.localPackets.get(topicHex)!;
103
+ const idx = existing.findIndex(p => p.agent_id === packet.agent_id);
104
+ if (idx >= 0) existing[idx] = packet;
105
+ else existing.push(packet);
106
+
107
+ // Push to connected peers on this topic
108
+ const sockets = this.connections.get(topicHex);
109
+ if (sockets) {
110
+ for (const socket of sockets) {
111
+ this.send(socket, { type: "PACKET", data: packet });
112
+ }
113
+ }
114
+
115
+ // Push to relay (fire and forget)
116
+ try {
117
+ fetch(`${RELAY_URL}/packets`, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify({
121
+ bucket: bucketPath,
122
+ agent_id: packet.agent_id,
123
+ outcome: {
124
+ signal: packet.signal,
125
+ confidence: packet.confidence,
126
+ insight: packet.insight,
127
+ context: packet.context,
128
+ metrics: packet.metrics,
129
+ },
130
+ }),
131
+ }).catch(() => {});
132
+ } catch {}
133
+ }
134
+
135
+ /**
136
+ * Query a bucket — pull ALL packets from DHT peers + relay, return everything
137
+ * The querying node tallies locally. We just deliver the raw packets.
138
+ */
139
+ async query(bucketPath: string, timeoutMs: number = 8000): Promise<DHTPacket[]> {
140
+ const allPackets: Map<string, DHTPacket> = new Map(); // agent_id → packet (dedup)
141
+ const topicHex = this.hashBucket(bucketPath).toString("hex");
142
+
143
+ // 1. Local packets first (instant)
144
+ const local = this.localPackets.get(topicHex) || [];
145
+ for (const p of local) allPackets.set(p.agent_id, p);
146
+
147
+ // 2. Previously collected peer packets
148
+ const cached = this.peerPackets.get(topicHex) || [];
149
+ for (const p of cached) allPackets.set(p.agent_id, p);
150
+
151
+ // 3. DHT peers (stream packets as connections come in)
152
+ if (this.swarm && this.ready) {
153
+ try {
154
+ const topic = Buffer.from(topicHex, "hex");
155
+ const discovery = this.swarm.join(topic, { server: false, client: true });
156
+
157
+ await Promise.race([
158
+ this.collectFromPeers(topicHex, allPackets, timeoutMs),
159
+ new Promise(r => setTimeout(r, timeoutMs)),
160
+ ]);
161
+ } catch {}
162
+ }
163
+
164
+ // 4. Relay fallback (always check — it has the canonical copy)
165
+ try {
166
+ const res = await fetch(`${RELAY_URL}/lookup?path=${encodeURIComponent(bucketPath)}`);
167
+ if (res.ok) {
168
+ const data = await res.json();
169
+ for (const p of data.packets || []) {
170
+ if (!allPackets.has(p.agent_id)) {
171
+ allPackets.set(p.agent_id, {
172
+ bucket: bucketPath,
173
+ agent_id: p.agent_id,
174
+ signal: p.signal,
175
+ confidence: p.confidence,
176
+ insight: p.insight,
177
+ context: typeof p.context === "string" ? JSON.parse(p.context) : p.context,
178
+ metrics: typeof p.metrics === "string" ? JSON.parse(p.metrics) : p.metrics,
179
+ ts: p.ts,
180
+ });
181
+ }
182
+ }
183
+ }
184
+ } catch {}
185
+
186
+ return Array.from(allPackets.values());
187
+ }
188
+
189
+ /**
190
+ * Search for buckets by keyword — uses relay (DHT doesn't support text search)
191
+ */
192
+ async search(keyword: string): Promise<any[]> {
193
+ try {
194
+ const res = await fetch(`${RELAY_URL}/buckets?q=${encodeURIComponent(keyword)}`);
195
+ if (res.ok) {
196
+ const data = await res.json();
197
+ return data.buckets || [];
198
+ }
199
+ } catch {}
200
+ return [];
201
+ }
202
+
203
+ /**
204
+ * Tally packets locally — the edge synthesis
205
+ * This is where QIS happens: count what's working from your exact twins
206
+ */
207
+ static tally(packets: DHTPacket[]): {
208
+ total: number;
209
+ positive: number;
210
+ negative: number;
211
+ neutral: number;
212
+ positive_pct: number;
213
+ avg_confidence: number;
214
+ top_insights: Array<{ insight: string; count: number; avg_confidence: number }>;
215
+ } {
216
+ const total = packets.length;
217
+ const positive = packets.filter(p => p.signal === "positive").length;
218
+ const negative = packets.filter(p => p.signal === "negative").length;
219
+ const neutral = total - positive - negative;
220
+ const avgConf = total > 0 ? packets.reduce((s, p) => s + p.confidence, 0) / total : 0;
221
+
222
+ // Group similar insights and count
223
+ const insightGroups: Map<string, { count: number; totalConf: number }> = new Map();
224
+ for (const p of packets) {
225
+ // Normalize insight to first 100 chars lowercase for grouping
226
+ const key = p.insight.toLowerCase().slice(0, 100).trim();
227
+ const group = insightGroups.get(key) || { count: 0, totalConf: 0 };
228
+ group.count++;
229
+ group.totalConf += p.confidence;
230
+ insightGroups.set(key, group);
231
+ }
232
+
233
+ const topInsights = Array.from(insightGroups.entries())
234
+ .map(([insight, g]) => ({ insight, count: g.count, avg_confidence: g.totalConf / g.count }))
235
+ .sort((a, b) => b.count * b.avg_confidence - a.count * a.avg_confidence)
236
+ .slice(0, 10);
237
+
238
+ return {
239
+ total,
240
+ positive,
241
+ negative,
242
+ neutral,
243
+ positive_pct: total > 0 ? Math.round((positive / total) * 100) : 0,
244
+ avg_confidence: Math.round(avgConf * 100) / 100,
245
+ top_insights: topInsights,
246
+ };
247
+ }
248
+
249
+ // --- Connection Handling ---
250
+
251
+ private handleConnection(socket: any, info: any) {
252
+ const peerId = info.publicKey?.toString("hex").slice(0, 12) || "unknown";
253
+ let buffer = "";
254
+
255
+ socket.on("data", (chunk: Buffer) => {
256
+ buffer += chunk.toString();
257
+ const lines = buffer.split("\n");
258
+ buffer = lines.pop() || "";
259
+ for (const line of lines) {
260
+ if (line.trim()) {
261
+ try { this.handleMessage(socket, info, JSON.parse(line)); } catch {}
262
+ }
263
+ }
264
+ });
265
+
266
+ socket.on("error", () => {});
267
+ socket.on("close", () => {
268
+ // Remove from connections
269
+ for (const [topic, sockets] of this.connections) {
270
+ sockets.delete(socket);
271
+ }
272
+ });
273
+
274
+ // Track connection by topics
275
+ for (const topic of info.topics || []) {
276
+ const hex = topic.toString("hex");
277
+ if (!this.connections.has(hex)) this.connections.set(hex, new Set());
278
+ this.connections.get(hex)!.add(socket);
279
+ }
280
+
281
+ // Send our packets for shared topics
282
+ for (const topic of info.topics || []) {
283
+ const hex = topic.toString("hex");
284
+ const myPackets = this.localPackets.get(hex) || [];
285
+ for (const p of myPackets) {
286
+ this.send(socket, { type: "PACKET", data: p });
287
+ }
288
+ }
289
+
290
+ // Request their packets
291
+ this.send(socket, { type: "GET_PACKETS" });
292
+ }
293
+
294
+ private handleMessage(socket: any, info: any, msg: any) {
295
+ switch (msg.type) {
296
+ case "PACKET":
297
+ // Store peer's packet
298
+ if (msg.data && msg.data.agent_id) {
299
+ const bucket = msg.data.bucket || "";
300
+ const topicHex = this.hashBucket(bucket).toString("hex");
301
+ if (!this.peerPackets.has(topicHex)) this.peerPackets.set(topicHex, []);
302
+ const packets = this.peerPackets.get(topicHex)!;
303
+ const idx = packets.findIndex(p => p.agent_id === msg.data.agent_id);
304
+ if (idx >= 0) packets[idx] = msg.data;
305
+ else packets.push(msg.data);
306
+ }
307
+ break;
308
+
309
+ case "GET_PACKETS":
310
+ // Send all our local packets for shared topics
311
+ for (const topic of info.topics || []) {
312
+ const hex = topic.toString("hex");
313
+ const myPackets = this.localPackets.get(hex) || [];
314
+ for (const p of myPackets) {
315
+ this.send(socket, { type: "PACKET", data: p });
316
+ }
317
+ }
318
+ break;
319
+ }
320
+ }
321
+
322
+ private async collectFromPeers(topicHex: string, allPackets: Map<string, DHTPacket>, timeoutMs: number): Promise<void> {
323
+ const start = Date.now();
324
+ while (Date.now() - start < timeoutMs) {
325
+ const cached = this.peerPackets.get(topicHex) || [];
326
+ for (const p of cached) allPackets.set(p.agent_id, p);
327
+ await new Promise(r => setTimeout(r, 500));
328
+ }
329
+ }
330
+
331
+ private send(socket: any, msg: any) {
332
+ try { socket.write(JSON.stringify(msg) + "\n"); } catch {}
333
+ }
334
+
335
+ // --- Persistence ---
336
+
337
+ private getOrCreateKeypair(): { publicKey: Buffer; secretKey: Buffer } {
338
+ try {
339
+ if (fs.existsSync(KEYPAIR_PATH)) {
340
+ const saved = JSON.parse(fs.readFileSync(KEYPAIR_PATH, "utf-8"));
341
+ return {
342
+ publicKey: Buffer.from(saved.publicKey, "hex"),
343
+ secretKey: Buffer.from(saved.secretKey, "hex"),
344
+ };
345
+ }
346
+ } catch {}
347
+
348
+ // Let Hyperswarm generate one, save it
349
+ const tempSwarm = new Hyperswarm();
350
+ const kp = tempSwarm.keyPair;
351
+ fs.mkdirSync(path.dirname(KEYPAIR_PATH), { recursive: true });
352
+ fs.writeFileSync(KEYPAIR_PATH, JSON.stringify({
353
+ publicKey: kp.publicKey.toString("hex"),
354
+ secretKey: kp.secretKey.toString("hex"),
355
+ }));
356
+ tempSwarm.destroy();
357
+ return kp;
358
+ }
359
+
360
+ private hashBucket(bucketPath: string): Buffer {
361
+ return crypto.createHash("sha256").update(bucketPath).digest();
362
+ }
363
+
364
+ private loadTopics(): string[] {
365
+ try { return JSON.parse(fs.readFileSync(TOPICS_PATH, "utf-8")); } catch { return []; }
366
+ }
367
+
368
+ private saveTopicList(newTopicHex: string) {
369
+ const topics = this.loadTopics();
370
+ if (!topics.includes(newTopicHex)) {
371
+ topics.push(newTopicHex);
372
+ fs.mkdirSync(path.dirname(TOPICS_PATH), { recursive: true });
373
+ fs.writeFileSync(TOPICS_PATH, JSON.stringify(topics));
374
+ }
375
+ }
376
+ }