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.
- package/LICENSE +44 -0
- package/README.md +282 -0
- package/docs/assets/favicon.png +0 -0
- package/docs/assets/metaclaw-banner.svg +86 -0
- package/docs/assets/qis-logo.png +0 -0
- package/docs/assets/yz-favicon.png +0 -0
- package/docs/assets/yz-logo.png +0 -0
- package/docs/index.html +895 -0
- package/installer/assets/favicon.png +0 -0
- package/installer/auto-start.ts +330 -0
- package/installer/brand.ts +115 -0
- package/installer/core-scaffold.ts +448 -0
- package/installer/dashboard-generator.ts +657 -0
- package/installer/detect.ts +129 -0
- package/installer/index.ts +355 -0
- package/installer/module-loader.ts +412 -0
- package/installer/modules/boardroom/boardroom/client.ts.txt +201 -0
- package/installer/modules/boardroom/boardroom/db.ts.txt +322 -0
- package/installer/modules/boardroom/boardroom/meeting-agent.ts.txt +129 -0
- package/installer/modules/boardroom/boardroom/meeting-scheduler.ts.txt +194 -0
- package/installer/modules/boardroom/boardroom/server.ts.txt +473 -0
- package/installer/modules/boardroom/boardroom/start-boardroom.bat.txt +26 -0
- package/installer/modules/boardroom/boardroom/summons.ts.txt +76 -0
- package/installer/modules/boardroom/boardroom/turn-v2.ts.txt +172 -0
- package/installer/modules/boardroom/boardroom/turn.ts.txt +208 -0
- package/installer/modules/boardroom/boardroom/types.ts.txt +100 -0
- package/installer/modules/boardroom/metaclaw-module.json +35 -0
- package/installer/modules/boardroom/scripts/meeting-check.bat.txt +38 -0
- package/installer/modules/core/metaclaw-module.json +51 -0
- package/installer/modules/core/src/db.ts.txt +277 -0
- package/installer/modules/core/src/health-check.ts.txt +128 -0
- package/installer/modules/core/src/observability.ts.txt +20 -0
- package/installer/modules/core/src/safety.ts.txt +26 -0
- package/installer/modules/core/src/scan-capabilities.ts.txt +196 -0
- package/installer/modules/core/src/self-improve.ts.txt +48 -0
- package/installer/modules/core/src/self-update.ts.txt +345 -0
- package/installer/modules/core/src/sync-context.ts.txt +133 -0
- package/installer/modules/core/src/tasks.ts.txt +159 -0
- package/installer/modules/custom/metaclaw-module.json +15 -0
- package/installer/modules/custom/src/agent-custom.ts.txt +100 -0
- package/installer/modules/dashboard/metaclaw-module.json +23 -0
- package/installer/modules/dashboard/scripts/build-dashboard.cjs.txt +51 -0
- package/installer/modules/dashboard/src/update-dashboard.ts.txt +126 -0
- package/installer/modules/outreach/metaclaw-module.json +29 -0
- package/installer/modules/outreach/src/agent-outreach.ts.txt +193 -0
- package/installer/modules/outreach/src/inbox-agent.ts.txt +283 -0
- package/installer/modules/outreach/src/morning-report.ts.txt +124 -0
- package/installer/modules/research/metaclaw-module.json +15 -0
- package/installer/modules/research/src/agent-research.ts.txt +127 -0
- package/installer/modules/scheduler/metaclaw-module.json +27 -0
- package/installer/modules/scheduler/scripts/agent-cycle.bat.txt +85 -0
- package/installer/modules/scheduler/scripts/detect-session.bat.txt +41 -0
- package/installer/modules/scheduler/scripts/launch.bat.txt +120 -0
- package/installer/modules/scheduler/src/cron-manager.ts.txt +273 -0
- package/installer/modules/social/metaclaw-module.json +15 -0
- package/installer/modules/social/src/agent-social.ts.txt +110 -0
- package/installer/modules/support/metaclaw-module.json +15 -0
- package/installer/modules/support/src/agent-support.ts.txt +60 -0
- package/installer/modules/swarm/metaclaw-module.json +25 -0
- package/installer/modules/swarm/swarm/dht-client.ts.txt +376 -0
- package/installer/modules/swarm/swarm/relay-server.ts.txt +348 -0
- package/installer/modules/swarm/swarm/swarm-client.ts.txt +303 -0
- package/installer/modules/swarm/swarm/types.ts.txt +51 -0
- package/installer/modules/voice/metaclaw-module.json +16 -0
- package/installer/questionnaire.ts +277 -0
- package/installer/research.ts +258 -0
- package/installer/scaffold-from-config.ts +270 -0
- package/installer/task-generator.ts +324 -0
- package/installer/templates/agent-custom.ts.txt +100 -0
- package/installer/templates/agent-cycle.bat.txt +19 -0
- package/installer/templates/agent-outreach.ts.txt +193 -0
- package/installer/templates/agent-research.ts.txt +127 -0
- package/installer/templates/agent-social.ts.txt +110 -0
- package/installer/templates/agent-support.ts.txt +60 -0
- package/installer/templates/build-dashboard.cjs.txt +51 -0
- package/installer/templates/cron-manager.ts.txt +273 -0
- package/installer/templates/dashboard.html.txt +450 -0
- package/installer/templates/db.ts.txt +277 -0
- package/installer/templates/detect-session.bat.txt +41 -0
- package/installer/templates/health-check.ts.txt +128 -0
- package/installer/templates/inbox-agent.ts.txt +283 -0
- package/installer/templates/launch.bat.txt +120 -0
- package/installer/templates/morning-report.ts.txt +124 -0
- package/installer/templates/observability.ts.txt +20 -0
- package/installer/templates/safety.ts.txt +26 -0
- package/installer/templates/self-improve.ts.txt +48 -0
- package/installer/templates/self-update.ts.txt +345 -0
- package/installer/templates/state.json.txt +33 -0
- package/installer/templates/system-context.json.txt +33 -0
- package/installer/templates/update-dashboard.ts.txt +126 -0
- package/package.json +31 -0
- 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
|
+
}
|