@ruvector/edge-net 0.1.0 → 0.1.2
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/README.md +119 -0
- package/cli.js +287 -108
- package/index.js +104 -0
- package/join.html +985 -0
- package/join.js +1333 -0
- package/network.js +820 -0
- package/networks.js +817 -0
- package/node/ruvector_edge_net.cjs +8126 -0
- package/node/ruvector_edge_net.d.ts +2289 -0
- package/node/ruvector_edge_net_bg.wasm +0 -0
- package/node/ruvector_edge_net_bg.wasm.d.ts +625 -0
- package/package.json +17 -3
- package/webrtc.js +964 -0
package/join.js
ADDED
|
@@ -0,0 +1,1333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @ruvector/edge-net Join CLI
|
|
4
|
+
*
|
|
5
|
+
* Simple CLI to join the EdgeNet distributed compute network with public key support.
|
|
6
|
+
* Supports multiple contributors connecting with their own identities.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @ruvector/edge-net join # Generate new identity and join
|
|
10
|
+
* npx @ruvector/edge-net join --key <pubkey> # Join with existing public key
|
|
11
|
+
* npx @ruvector/edge-net join --generate # Generate new keypair only
|
|
12
|
+
* npx @ruvector/edge-net join --export # Export identity for sharing
|
|
13
|
+
* npx @ruvector/edge-net join --import <file> # Import identity from backup
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname, join } from 'path';
|
|
19
|
+
import { webcrypto } from 'crypto';
|
|
20
|
+
import { performance } from 'perf_hooks';
|
|
21
|
+
import { homedir } from 'os';
|
|
22
|
+
import { NetworkManager } from './network.js';
|
|
23
|
+
import { MultiNetworkManager, NetworkRegistry } from './networks.js';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = dirname(__filename);
|
|
27
|
+
|
|
28
|
+
// Setup polyfills
|
|
29
|
+
async function setupPolyfills() {
|
|
30
|
+
if (typeof globalThis.crypto === 'undefined') {
|
|
31
|
+
globalThis.crypto = webcrypto;
|
|
32
|
+
}
|
|
33
|
+
if (typeof globalThis.performance === 'undefined') {
|
|
34
|
+
globalThis.performance = performance;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const createStorage = () => {
|
|
38
|
+
const store = new Map();
|
|
39
|
+
return {
|
|
40
|
+
getItem: (key) => store.get(key) || null,
|
|
41
|
+
setItem: (key, value) => store.set(key, String(value)),
|
|
42
|
+
removeItem: (key) => store.delete(key),
|
|
43
|
+
clear: () => store.clear(),
|
|
44
|
+
get length() { return store.size; },
|
|
45
|
+
key: (i) => [...store.keys()][i] || null,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let cpuCount = 4;
|
|
50
|
+
try {
|
|
51
|
+
const os = await import('os');
|
|
52
|
+
cpuCount = os.cpus().length;
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
if (typeof globalThis.window === 'undefined') {
|
|
56
|
+
globalThis.window = {
|
|
57
|
+
crypto: globalThis.crypto,
|
|
58
|
+
performance: globalThis.performance,
|
|
59
|
+
localStorage: createStorage(),
|
|
60
|
+
sessionStorage: createStorage(),
|
|
61
|
+
navigator: {
|
|
62
|
+
userAgent: `Node.js/${process.version}`,
|
|
63
|
+
language: 'en-US',
|
|
64
|
+
languages: ['en-US', 'en'],
|
|
65
|
+
hardwareConcurrency: cpuCount,
|
|
66
|
+
},
|
|
67
|
+
location: { href: 'node://localhost', hostname: 'localhost' },
|
|
68
|
+
screen: { width: 1920, height: 1080, colorDepth: 24 },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof globalThis.document === 'undefined') {
|
|
73
|
+
globalThis.document = {
|
|
74
|
+
createElement: () => ({}),
|
|
75
|
+
body: {},
|
|
76
|
+
head: {},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ANSI colors
|
|
82
|
+
const colors = {
|
|
83
|
+
reset: '\x1b[0m',
|
|
84
|
+
bold: '\x1b[1m',
|
|
85
|
+
dim: '\x1b[2m',
|
|
86
|
+
cyan: '\x1b[36m',
|
|
87
|
+
green: '\x1b[32m',
|
|
88
|
+
yellow: '\x1b[33m',
|
|
89
|
+
blue: '\x1b[34m',
|
|
90
|
+
magenta: '\x1b[35m',
|
|
91
|
+
red: '\x1b[31m',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
|
|
95
|
+
|
|
96
|
+
function printBanner() {
|
|
97
|
+
console.log(`
|
|
98
|
+
${c('cyan', '╔═══════════════════════════════════════════════════════════════╗')}
|
|
99
|
+
${c('cyan', '║')} ${c('bold', '🔗 RuVector Edge-Net Join')} ${c('cyan', '║')}
|
|
100
|
+
${c('cyan', '║')} ${c('dim', 'Join the Distributed Compute Network')} ${c('cyan', '║')}
|
|
101
|
+
${c('cyan', '╚═══════════════════════════════════════════════════════════════╝')}
|
|
102
|
+
`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printHelp() {
|
|
106
|
+
printBanner();
|
|
107
|
+
console.log(`${c('bold', 'USAGE:')}
|
|
108
|
+
${c('green', 'npx @ruvector/edge-net join')} [options]
|
|
109
|
+
|
|
110
|
+
${c('bold', 'IDENTITY OPTIONS:')}
|
|
111
|
+
${c('yellow', '--generate')} Generate new Pi-Key identity without joining
|
|
112
|
+
${c('yellow', '--key <pubkey>')} Join using existing public key (hex)
|
|
113
|
+
${c('yellow', '--site <id>')} Set site identifier (default: "edge-contributor")
|
|
114
|
+
${c('yellow', '--export <file>')} Export identity to encrypted file
|
|
115
|
+
${c('yellow', '--import <file>')} Import identity from encrypted backup
|
|
116
|
+
${c('yellow', '--password <pw>')} Password for import/export operations
|
|
117
|
+
${c('yellow', '--status')} Show current contributor status
|
|
118
|
+
${c('yellow', '--history')} Show contribution history
|
|
119
|
+
${c('yellow', '--list')} List all stored identities
|
|
120
|
+
${c('yellow', '--peers')} List connected peers
|
|
121
|
+
|
|
122
|
+
${c('bold', 'MULTI-NETWORK OPTIONS:')}
|
|
123
|
+
${c('yellow', '--networks')} List all known networks
|
|
124
|
+
${c('yellow', '--discover')} Discover available networks
|
|
125
|
+
${c('yellow', '--network <id>')} Join/use specific network by ID
|
|
126
|
+
${c('yellow', '--create-network')} Create a new network with name
|
|
127
|
+
${c('yellow', '--network-type')} Network type: public, private, consortium
|
|
128
|
+
${c('yellow', '--network-desc')} Description for new network
|
|
129
|
+
${c('yellow', '--switch <id>')} Switch active network
|
|
130
|
+
${c('yellow', '--invite <code>')} Invite code for private networks
|
|
131
|
+
|
|
132
|
+
${c('bold', 'EXAMPLES:')}
|
|
133
|
+
${c('dim', '# Generate new identity and join default network')}
|
|
134
|
+
$ npx @ruvector/edge-net join
|
|
135
|
+
|
|
136
|
+
${c('dim', '# Discover available networks')}
|
|
137
|
+
$ npx @ruvector/edge-net join --discover
|
|
138
|
+
|
|
139
|
+
${c('dim', '# Create a public research network')}
|
|
140
|
+
$ npx @ruvector/edge-net join --create-network "ML Research" --network-desc "For ML workloads"
|
|
141
|
+
|
|
142
|
+
${c('dim', '# Create a private team network')}
|
|
143
|
+
$ npx @ruvector/edge-net join --create-network "Team Alpha" --network-type private
|
|
144
|
+
|
|
145
|
+
${c('dim', '# Join a specific network')}
|
|
146
|
+
$ npx @ruvector/edge-net join --network net-abc123
|
|
147
|
+
|
|
148
|
+
${c('dim', '# Join a private network with invite code')}
|
|
149
|
+
$ npx @ruvector/edge-net join --network net-xyz789 --invite <invite-code>
|
|
150
|
+
|
|
151
|
+
${c('dim', '# Switch active network')}
|
|
152
|
+
$ npx @ruvector/edge-net join --switch net-abc123
|
|
153
|
+
|
|
154
|
+
${c('bold', 'MULTI-CONTRIBUTOR SETUP:')}
|
|
155
|
+
Each contributor runs their own node with a unique identity.
|
|
156
|
+
|
|
157
|
+
${c('dim', 'Contributor 1:')}
|
|
158
|
+
$ npx @ruvector/edge-net join --site contributor-1
|
|
159
|
+
|
|
160
|
+
${c('dim', 'Contributor 2:')}
|
|
161
|
+
$ npx @ruvector/edge-net join --site contributor-2
|
|
162
|
+
|
|
163
|
+
${c('dim', 'All nodes automatically discover and connect via P2P gossip.')}
|
|
164
|
+
|
|
165
|
+
${c('bold', 'NETWORK TYPES:')}
|
|
166
|
+
${c('cyan', '🌐 Public')} Anyone can join and discover
|
|
167
|
+
${c('cyan', '🔒 Private')} Requires invite code to join
|
|
168
|
+
${c('cyan', '🏢 Consortium')} Requires approval from existing members
|
|
169
|
+
|
|
170
|
+
${c('bold', 'IDENTITY INFO:')}
|
|
171
|
+
${c('cyan', 'Pi-Key:')} 40-byte Ed25519-based identity (π-sized)
|
|
172
|
+
${c('cyan', 'Public Key:')} 32-byte Ed25519 verification key
|
|
173
|
+
${c('cyan', 'Genesis ID:')} 21-byte network fingerprint (φ-sized)
|
|
174
|
+
|
|
175
|
+
${c('dim', 'Documentation: https://github.com/ruvnet/ruvector/tree/main/examples/edge-net')}
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Config directory for storing identities - persistent across months/years
|
|
180
|
+
function getConfigDir() {
|
|
181
|
+
const configDir = join(homedir(), '.ruvector');
|
|
182
|
+
if (!existsSync(configDir)) {
|
|
183
|
+
mkdirSync(configDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
return configDir;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getIdentitiesDir() {
|
|
189
|
+
const identitiesDir = join(getConfigDir(), 'identities');
|
|
190
|
+
if (!existsSync(identitiesDir)) {
|
|
191
|
+
mkdirSync(identitiesDir, { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
return identitiesDir;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getContributionsDir() {
|
|
197
|
+
const contribDir = join(getConfigDir(), 'contributions');
|
|
198
|
+
if (!existsSync(contribDir)) {
|
|
199
|
+
mkdirSync(contribDir, { recursive: true });
|
|
200
|
+
}
|
|
201
|
+
return contribDir;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Long-term persistent identity management
|
|
205
|
+
class PersistentIdentity {
|
|
206
|
+
constructor(siteId, wasm) {
|
|
207
|
+
this.siteId = siteId;
|
|
208
|
+
this.wasm = wasm;
|
|
209
|
+
this.identityPath = join(getIdentitiesDir(), `${siteId}.identity`);
|
|
210
|
+
this.metaPath = join(getIdentitiesDir(), `${siteId}.meta.json`);
|
|
211
|
+
this.contributionPath = join(getContributionsDir(), `${siteId}.history.json`);
|
|
212
|
+
this.piKey = null;
|
|
213
|
+
this.meta = null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
exists() {
|
|
217
|
+
return existsSync(this.identityPath);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Generate new or restore existing identity
|
|
221
|
+
async initialize(password) {
|
|
222
|
+
if (this.exists()) {
|
|
223
|
+
return this.restore(password);
|
|
224
|
+
} else {
|
|
225
|
+
return this.generate(password);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Generate new identity with full metadata
|
|
230
|
+
generate(password) {
|
|
231
|
+
this.piKey = new this.wasm.PiKey();
|
|
232
|
+
|
|
233
|
+
// Save encrypted identity
|
|
234
|
+
const backup = this.piKey.createEncryptedBackup(password);
|
|
235
|
+
writeFileSync(this.identityPath, Buffer.from(backup));
|
|
236
|
+
|
|
237
|
+
// Save metadata (not secret)
|
|
238
|
+
this.meta = {
|
|
239
|
+
version: 1,
|
|
240
|
+
siteId: this.siteId,
|
|
241
|
+
shortId: this.piKey.getShortId(),
|
|
242
|
+
publicKey: toHex(this.piKey.getPublicKey()),
|
|
243
|
+
genesisFingerprint: toHex(this.piKey.getGenesisFingerprint()),
|
|
244
|
+
createdAt: new Date().toISOString(),
|
|
245
|
+
lastUsed: new Date().toISOString(),
|
|
246
|
+
totalSessions: 1,
|
|
247
|
+
totalContributions: 0
|
|
248
|
+
};
|
|
249
|
+
writeFileSync(this.metaPath, JSON.stringify(this.meta, null, 2));
|
|
250
|
+
|
|
251
|
+
// Initialize contribution history
|
|
252
|
+
const history = {
|
|
253
|
+
siteId: this.siteId,
|
|
254
|
+
shortId: this.meta.shortId,
|
|
255
|
+
sessions: [{
|
|
256
|
+
started: new Date().toISOString(),
|
|
257
|
+
type: 'genesis'
|
|
258
|
+
}],
|
|
259
|
+
contributions: [],
|
|
260
|
+
milestones: [{
|
|
261
|
+
type: 'identity_created',
|
|
262
|
+
timestamp: new Date().toISOString()
|
|
263
|
+
}]
|
|
264
|
+
};
|
|
265
|
+
writeFileSync(this.contributionPath, JSON.stringify(history, null, 2));
|
|
266
|
+
|
|
267
|
+
return { isNew: true, meta: this.meta };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Restore existing identity
|
|
271
|
+
restore(password) {
|
|
272
|
+
const backup = new Uint8Array(readFileSync(this.identityPath));
|
|
273
|
+
this.piKey = this.wasm.PiKey.restoreFromBackup(backup, password);
|
|
274
|
+
|
|
275
|
+
// Load and update metadata
|
|
276
|
+
if (existsSync(this.metaPath)) {
|
|
277
|
+
this.meta = JSON.parse(readFileSync(this.metaPath, 'utf-8'));
|
|
278
|
+
} else {
|
|
279
|
+
// Rebuild metadata from key
|
|
280
|
+
this.meta = {
|
|
281
|
+
version: 1,
|
|
282
|
+
siteId: this.siteId,
|
|
283
|
+
shortId: this.piKey.getShortId(),
|
|
284
|
+
publicKey: toHex(this.piKey.getPublicKey()),
|
|
285
|
+
genesisFingerprint: toHex(this.piKey.getGenesisFingerprint()),
|
|
286
|
+
createdAt: 'unknown',
|
|
287
|
+
lastUsed: new Date().toISOString(),
|
|
288
|
+
totalSessions: 1,
|
|
289
|
+
totalContributions: 0
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Update usage stats
|
|
294
|
+
this.meta.lastUsed = new Date().toISOString();
|
|
295
|
+
this.meta.totalSessions = (this.meta.totalSessions || 0) + 1;
|
|
296
|
+
writeFileSync(this.metaPath, JSON.stringify(this.meta, null, 2));
|
|
297
|
+
|
|
298
|
+
// Update contribution history
|
|
299
|
+
let history;
|
|
300
|
+
if (existsSync(this.contributionPath)) {
|
|
301
|
+
history = JSON.parse(readFileSync(this.contributionPath, 'utf-8'));
|
|
302
|
+
} else {
|
|
303
|
+
history = {
|
|
304
|
+
siteId: this.siteId,
|
|
305
|
+
shortId: this.meta.shortId,
|
|
306
|
+
sessions: [],
|
|
307
|
+
contributions: [],
|
|
308
|
+
milestones: []
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Calculate time since last session
|
|
313
|
+
const lastSession = history.sessions[history.sessions.length - 1];
|
|
314
|
+
let timeSinceLastSession = null;
|
|
315
|
+
if (lastSession && lastSession.started) {
|
|
316
|
+
const last = new Date(lastSession.started);
|
|
317
|
+
const now = new Date();
|
|
318
|
+
const diffMs = now - last;
|
|
319
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
320
|
+
timeSinceLastSession = diffDays;
|
|
321
|
+
|
|
322
|
+
if (diffDays > 30) {
|
|
323
|
+
history.milestones.push({
|
|
324
|
+
type: 'returned_after_absence',
|
|
325
|
+
timestamp: new Date().toISOString(),
|
|
326
|
+
daysSinceLastSession: diffDays
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
history.sessions.push({
|
|
332
|
+
started: new Date().toISOString(),
|
|
333
|
+
type: 'restored',
|
|
334
|
+
timeSinceLastDays: timeSinceLastSession
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
writeFileSync(this.contributionPath, JSON.stringify(history, null, 2));
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
isNew: false,
|
|
341
|
+
meta: this.meta,
|
|
342
|
+
sessions: this.meta.totalSessions,
|
|
343
|
+
daysSinceLastSession: timeSinceLastSession
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Record a contribution
|
|
348
|
+
recordContribution(type, details = {}) {
|
|
349
|
+
this.meta.totalContributions = (this.meta.totalContributions || 0) + 1;
|
|
350
|
+
this.meta.lastUsed = new Date().toISOString();
|
|
351
|
+
writeFileSync(this.metaPath, JSON.stringify(this.meta, null, 2));
|
|
352
|
+
|
|
353
|
+
let history = { sessions: [], contributions: [], milestones: [] };
|
|
354
|
+
if (existsSync(this.contributionPath)) {
|
|
355
|
+
history = JSON.parse(readFileSync(this.contributionPath, 'utf-8'));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
history.contributions.push({
|
|
359
|
+
type,
|
|
360
|
+
timestamp: new Date().toISOString(),
|
|
361
|
+
...details
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
writeFileSync(this.contributionPath, JSON.stringify(history, null, 2));
|
|
365
|
+
return this.meta.totalContributions;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Get full history
|
|
369
|
+
getHistory() {
|
|
370
|
+
if (!existsSync(this.contributionPath)) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
return JSON.parse(readFileSync(this.contributionPath, 'utf-8'));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Get public info for sharing
|
|
377
|
+
getPublicInfo() {
|
|
378
|
+
return {
|
|
379
|
+
siteId: this.siteId,
|
|
380
|
+
shortId: this.meta.shortId,
|
|
381
|
+
publicKey: this.meta.publicKey,
|
|
382
|
+
genesisFingerprint: this.meta.genesisFingerprint,
|
|
383
|
+
memberSince: this.meta.createdAt,
|
|
384
|
+
totalContributions: this.meta.totalContributions
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
free() {
|
|
389
|
+
if (this.piKey) this.piKey.free();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// List all stored identities
|
|
394
|
+
function listStoredIdentities() {
|
|
395
|
+
const identitiesDir = getIdentitiesDir();
|
|
396
|
+
if (!existsSync(identitiesDir)) return [];
|
|
397
|
+
|
|
398
|
+
const files = readdirSync(identitiesDir);
|
|
399
|
+
const identities = [];
|
|
400
|
+
|
|
401
|
+
for (const file of files) {
|
|
402
|
+
if (file.endsWith('.meta.json')) {
|
|
403
|
+
const meta = JSON.parse(readFileSync(join(identitiesDir, file), 'utf-8'));
|
|
404
|
+
identities.push(meta);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return identities;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function toHex(bytes) {
|
|
412
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function fromHex(hex) {
|
|
416
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
417
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
418
|
+
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
|
419
|
+
}
|
|
420
|
+
return bytes;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Parse arguments
|
|
424
|
+
function parseArgs(args) {
|
|
425
|
+
const opts = {
|
|
426
|
+
generate: false,
|
|
427
|
+
key: null,
|
|
428
|
+
site: 'edge-contributor',
|
|
429
|
+
export: null,
|
|
430
|
+
import: null,
|
|
431
|
+
password: null,
|
|
432
|
+
status: false,
|
|
433
|
+
history: false,
|
|
434
|
+
list: false,
|
|
435
|
+
peers: false,
|
|
436
|
+
help: false,
|
|
437
|
+
// Multi-network options
|
|
438
|
+
network: null, // Network ID to join/use
|
|
439
|
+
createNetwork: null, // Create new network with name
|
|
440
|
+
networkType: 'public', // public, private, consortium
|
|
441
|
+
networkDesc: null, // Network description
|
|
442
|
+
discoverNetworks: false, // Discover available networks
|
|
443
|
+
listNetworks: false, // List known networks
|
|
444
|
+
switchNetwork: null, // Switch active network
|
|
445
|
+
invite: null, // Invite code for private networks
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
for (let i = 0; i < args.length; i++) {
|
|
449
|
+
const arg = args[i];
|
|
450
|
+
switch (arg) {
|
|
451
|
+
case '--generate':
|
|
452
|
+
opts.generate = true;
|
|
453
|
+
break;
|
|
454
|
+
case '--key':
|
|
455
|
+
opts.key = args[++i];
|
|
456
|
+
break;
|
|
457
|
+
case '--site':
|
|
458
|
+
opts.site = args[++i];
|
|
459
|
+
break;
|
|
460
|
+
case '--export':
|
|
461
|
+
opts.export = args[++i];
|
|
462
|
+
break;
|
|
463
|
+
case '--import':
|
|
464
|
+
opts.import = args[++i];
|
|
465
|
+
break;
|
|
466
|
+
case '--password':
|
|
467
|
+
opts.password = args[++i];
|
|
468
|
+
break;
|
|
469
|
+
case '--status':
|
|
470
|
+
opts.status = true;
|
|
471
|
+
break;
|
|
472
|
+
case '--history':
|
|
473
|
+
opts.history = true;
|
|
474
|
+
break;
|
|
475
|
+
case '--list':
|
|
476
|
+
opts.list = true;
|
|
477
|
+
break;
|
|
478
|
+
case '--peers':
|
|
479
|
+
opts.peers = true;
|
|
480
|
+
break;
|
|
481
|
+
// Multi-network options
|
|
482
|
+
case '--network':
|
|
483
|
+
case '-n':
|
|
484
|
+
opts.network = args[++i];
|
|
485
|
+
break;
|
|
486
|
+
case '--create-network':
|
|
487
|
+
opts.createNetwork = args[++i];
|
|
488
|
+
break;
|
|
489
|
+
case '--network-type':
|
|
490
|
+
opts.networkType = args[++i];
|
|
491
|
+
break;
|
|
492
|
+
case '--network-desc':
|
|
493
|
+
opts.networkDesc = args[++i];
|
|
494
|
+
break;
|
|
495
|
+
case '--discover':
|
|
496
|
+
opts.discoverNetworks = true;
|
|
497
|
+
break;
|
|
498
|
+
case '--networks':
|
|
499
|
+
opts.listNetworks = true;
|
|
500
|
+
break;
|
|
501
|
+
case '--switch':
|
|
502
|
+
opts.switchNetwork = args[++i];
|
|
503
|
+
break;
|
|
504
|
+
case '--invite':
|
|
505
|
+
opts.invite = args[++i];
|
|
506
|
+
break;
|
|
507
|
+
case '--help':
|
|
508
|
+
case '-h':
|
|
509
|
+
opts.help = true;
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return opts;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Show contribution history
|
|
518
|
+
async function showHistory(wasm, siteId, password) {
|
|
519
|
+
console.log(`${c('bold', 'CONTRIBUTION HISTORY:')}\n`);
|
|
520
|
+
|
|
521
|
+
const identity = new PersistentIdentity(siteId, wasm);
|
|
522
|
+
|
|
523
|
+
if (!identity.exists()) {
|
|
524
|
+
console.log(`${c('yellow', '⚠')} No identity found for site "${siteId}"`);
|
|
525
|
+
console.log(`${c('dim', 'Run without --history to create one.')}\n`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
await identity.initialize(password);
|
|
530
|
+
const history = identity.getHistory();
|
|
531
|
+
|
|
532
|
+
if (!history) {
|
|
533
|
+
console.log(`${c('dim', 'No history available.')}\n`);
|
|
534
|
+
identity.free();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
console.log(` ${c('cyan', 'Site ID:')} ${history.siteId}`);
|
|
539
|
+
console.log(` ${c('cyan', 'Short ID:')} ${history.shortId}`);
|
|
540
|
+
console.log(` ${c('cyan', 'Sessions:')} ${history.sessions.length}`);
|
|
541
|
+
console.log(` ${c('cyan', 'Contributions:')} ${history.contributions.length}`);
|
|
542
|
+
console.log(` ${c('cyan', 'Milestones:')} ${history.milestones.length}\n`);
|
|
543
|
+
|
|
544
|
+
if (history.milestones.length > 0) {
|
|
545
|
+
console.log(` ${c('bold', 'Milestones:')}`);
|
|
546
|
+
history.milestones.slice(-5).forEach(m => {
|
|
547
|
+
const date = new Date(m.timestamp).toLocaleDateString();
|
|
548
|
+
console.log(` ${c('dim', date)} - ${c('green', m.type)}`);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (history.sessions.length > 0) {
|
|
553
|
+
console.log(`\n ${c('bold', 'Recent Sessions:')}`);
|
|
554
|
+
history.sessions.slice(-5).forEach(s => {
|
|
555
|
+
const date = new Date(s.started).toLocaleDateString();
|
|
556
|
+
const time = new Date(s.started).toLocaleTimeString();
|
|
557
|
+
const elapsed = s.timeSinceLastDays ? ` (${s.timeSinceLastDays}d since last)` : '';
|
|
558
|
+
console.log(` ${c('dim', date + ' ' + time)} - ${s.type}${elapsed}`);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
console.log('');
|
|
563
|
+
identity.free();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// List all stored identities
|
|
567
|
+
async function listIdentities() {
|
|
568
|
+
console.log(`${c('bold', 'STORED IDENTITIES:')}\n`);
|
|
569
|
+
|
|
570
|
+
const identities = listStoredIdentities();
|
|
571
|
+
|
|
572
|
+
if (identities.length === 0) {
|
|
573
|
+
console.log(` ${c('dim', 'No identities found.')}`);
|
|
574
|
+
console.log(` ${c('dim', 'Run "npx @ruvector/edge-net join" to create one.')}\n`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
console.log(` ${c('cyan', 'Found')} ${identities.length} ${c('cyan', 'identities:')}\n`);
|
|
579
|
+
|
|
580
|
+
for (const meta of identities) {
|
|
581
|
+
const memberSince = meta.createdAt ? new Date(meta.createdAt).toLocaleDateString() : 'unknown';
|
|
582
|
+
const lastUsed = meta.lastUsed ? new Date(meta.lastUsed).toLocaleDateString() : 'unknown';
|
|
583
|
+
|
|
584
|
+
console.log(` ${c('bold', meta.siteId)}`);
|
|
585
|
+
console.log(` ${c('dim', 'ID:')} ${meta.shortId}`);
|
|
586
|
+
console.log(` ${c('dim', 'Public Key:')} ${meta.publicKey.substring(0, 16)}...`);
|
|
587
|
+
console.log(` ${c('dim', 'Member Since:')} ${memberSince}`);
|
|
588
|
+
console.log(` ${c('dim', 'Last Used:')} ${lastUsed}`);
|
|
589
|
+
console.log(` ${c('dim', 'Sessions:')} ${meta.totalSessions || 0}`);
|
|
590
|
+
console.log(` ${c('dim', 'Contributions:')} ${meta.totalContributions || 0}\n`);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
console.log(`${c('dim', 'Storage: ' + getIdentitiesDir())}\n`);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function generateIdentity(wasm, siteId) {
|
|
597
|
+
console.log(`${c('cyan', 'Generating new Pi-Key identity...')}\n`);
|
|
598
|
+
|
|
599
|
+
// Generate Pi-Key
|
|
600
|
+
const piKey = new wasm.PiKey();
|
|
601
|
+
|
|
602
|
+
const identity = piKey.getIdentity();
|
|
603
|
+
const identityHex = piKey.getIdentityHex();
|
|
604
|
+
const publicKey = piKey.getPublicKey();
|
|
605
|
+
const shortId = piKey.getShortId();
|
|
606
|
+
const genesisFingerprint = piKey.getGenesisFingerprint();
|
|
607
|
+
const hasPiMagic = piKey.verifyPiMagic();
|
|
608
|
+
const stats = JSON.parse(piKey.getStats());
|
|
609
|
+
|
|
610
|
+
console.log(`${c('bold', 'IDENTITY GENERATED:')}`);
|
|
611
|
+
console.log(` ${c('cyan', 'Short ID:')} ${shortId}`);
|
|
612
|
+
console.log(` ${c('cyan', 'Pi-Identity:')} ${identityHex.substring(0, 32)}...`);
|
|
613
|
+
console.log(` ${c('cyan', 'Public Key:')} ${toHex(publicKey).substring(0, 32)}...`);
|
|
614
|
+
console.log(` ${c('cyan', 'Genesis FP:')} ${toHex(genesisFingerprint)}`);
|
|
615
|
+
console.log(` ${c('cyan', 'Pi Magic:')} ${hasPiMagic ? c('green', '✓ Valid') : c('red', '✗ Invalid')}`);
|
|
616
|
+
console.log(` ${c('cyan', 'Identity Size:')} ${identity.length} bytes (π-sized)`);
|
|
617
|
+
console.log(` ${c('cyan', 'PubKey Size:')} ${publicKey.length} bytes`);
|
|
618
|
+
console.log(` ${c('cyan', 'Genesis Size:')} ${genesisFingerprint.length} bytes (φ-sized)\n`);
|
|
619
|
+
|
|
620
|
+
// Test signing
|
|
621
|
+
const testData = new TextEncoder().encode('EdgeNet contributor test message');
|
|
622
|
+
const signature = piKey.sign(testData);
|
|
623
|
+
const isValid = piKey.verify(testData, signature, publicKey);
|
|
624
|
+
|
|
625
|
+
console.log(`${c('bold', 'CRYPTOGRAPHIC TEST:')}`);
|
|
626
|
+
console.log(` ${c('cyan', 'Test Message:')} "EdgeNet contributor test message"`);
|
|
627
|
+
console.log(` ${c('cyan', 'Signature:')} ${toHex(signature).substring(0, 32)}...`);
|
|
628
|
+
console.log(` ${c('cyan', 'Signature Size:')} ${signature.length} bytes`);
|
|
629
|
+
console.log(` ${c('cyan', 'Verification:')} ${isValid ? c('green', '✓ Valid') : c('red', '✗ Invalid')}\n`);
|
|
630
|
+
|
|
631
|
+
return { piKey, publicKey, identityHex, shortId };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function exportIdentity(wasm, filePath, password) {
|
|
635
|
+
console.log(`${c('cyan', 'Exporting identity to:')} ${filePath}\n`);
|
|
636
|
+
|
|
637
|
+
const piKey = new wasm.PiKey();
|
|
638
|
+
|
|
639
|
+
if (!password) {
|
|
640
|
+
password = 'edge-net-default-password'; // Warning: use strong password in production
|
|
641
|
+
console.log(`${c('yellow', '⚠ Using default password. Use --password for security.')}\n`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const backup = piKey.createEncryptedBackup(password);
|
|
645
|
+
writeFileSync(filePath, Buffer.from(backup));
|
|
646
|
+
|
|
647
|
+
console.log(`${c('green', '✓')} Identity exported successfully`);
|
|
648
|
+
console.log(` ${c('cyan', 'File:')} ${filePath}`);
|
|
649
|
+
console.log(` ${c('cyan', 'Size:')} ${backup.length} bytes`);
|
|
650
|
+
console.log(` ${c('cyan', 'Encryption:')} Argon2id + AES-256-GCM`);
|
|
651
|
+
console.log(` ${c('cyan', 'Short ID:')} ${piKey.getShortId()}\n`);
|
|
652
|
+
|
|
653
|
+
console.log(`${c('yellow', 'Keep this file and password safe!')}`);
|
|
654
|
+
console.log(`${c('dim', 'You can restore with: npx @ruvector/edge-net join --import')} ${filePath}\n`);
|
|
655
|
+
|
|
656
|
+
return piKey;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function importIdentity(wasm, filePath, password) {
|
|
660
|
+
console.log(`${c('cyan', 'Importing identity from:')} ${filePath}\n`);
|
|
661
|
+
|
|
662
|
+
if (!existsSync(filePath)) {
|
|
663
|
+
console.error(`${c('red', '✗ File not found:')} ${filePath}`);
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (!password) {
|
|
668
|
+
password = 'edge-net-default-password';
|
|
669
|
+
console.log(`${c('yellow', '⚠ Using default password.')}\n`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const backup = new Uint8Array(readFileSync(filePath));
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
const piKey = wasm.PiKey.restoreFromBackup(backup, password);
|
|
676
|
+
|
|
677
|
+
console.log(`${c('green', '✓')} Identity restored successfully`);
|
|
678
|
+
console.log(` ${c('cyan', 'Short ID:')} ${piKey.getShortId()}`);
|
|
679
|
+
console.log(` ${c('cyan', 'Public Key:')} ${toHex(piKey.getPublicKey()).substring(0, 32)}...`);
|
|
680
|
+
console.log(` ${c('cyan', 'Pi Magic:')} ${piKey.verifyPiMagic() ? c('green', '✓ Valid') : c('red', '✗ Invalid')}\n`);
|
|
681
|
+
|
|
682
|
+
return piKey;
|
|
683
|
+
} catch (e) {
|
|
684
|
+
console.error(`${c('red', '✗ Failed to restore identity:')} ${e.message}`);
|
|
685
|
+
console.log(`${c('dim', 'Check password and file integrity.')}`);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async function joinNetwork(wasm, opts, piKey) {
|
|
691
|
+
console.log(`${c('bold', 'JOINING EDGE-NET...')}\n`);
|
|
692
|
+
|
|
693
|
+
const publicKeyHex = toHex(piKey.getPublicKey());
|
|
694
|
+
|
|
695
|
+
// Create components for network participation
|
|
696
|
+
const detector = new wasm.ByzantineDetector(0.5);
|
|
697
|
+
const dp = new wasm.DifferentialPrivacy(1.0, 0.001);
|
|
698
|
+
const model = new wasm.FederatedModel(100, 0.01, 0.9);
|
|
699
|
+
const coherence = new wasm.CoherenceEngine();
|
|
700
|
+
const evolution = new wasm.EvolutionEngine();
|
|
701
|
+
const events = new wasm.NetworkEvents();
|
|
702
|
+
|
|
703
|
+
console.log(`${c('bold', 'CONTRIBUTOR NODE:')}`);
|
|
704
|
+
console.log(` ${c('cyan', 'Site ID:')} ${opts.site}`);
|
|
705
|
+
console.log(` ${c('cyan', 'Short ID:')} ${piKey.getShortId()}`);
|
|
706
|
+
console.log(` ${c('cyan', 'Public Key:')} ${publicKeyHex.substring(0, 16)}...${publicKeyHex.slice(-8)}`);
|
|
707
|
+
console.log(` ${c('cyan', 'Status:')} ${c('green', 'Connected')}`);
|
|
708
|
+
console.log(` ${c('cyan', 'Mode:')} Lightweight (CLI)\n`);
|
|
709
|
+
|
|
710
|
+
console.log(`${c('bold', 'ACTIVE COMPONENTS:')}`);
|
|
711
|
+
console.log(` ${c('green', '✓')} Byzantine Detector (threshold=0.5)`);
|
|
712
|
+
console.log(` ${c('green', '✓')} Differential Privacy (ε=1.0)`);
|
|
713
|
+
console.log(` ${c('green', '✓')} Federated Model (dim=100)`);
|
|
714
|
+
console.log(` ${c('green', '✓')} Coherence Engine (Merkle: ${coherence.getMerkleRoot().substring(0, 16)}...)`);
|
|
715
|
+
console.log(` ${c('green', '✓')} Evolution Engine (fitness: ${evolution.getNetworkFitness().toFixed(2)})`);
|
|
716
|
+
|
|
717
|
+
// Get themed status
|
|
718
|
+
const themedStatus = events.getThemedStatus(1, BigInt(0));
|
|
719
|
+
console.log(`\n${c('bold', 'NETWORK STATUS:')}`);
|
|
720
|
+
console.log(` ${themedStatus}\n`);
|
|
721
|
+
|
|
722
|
+
// Show sharing information
|
|
723
|
+
console.log(`${c('bold', 'SHARE YOUR PUBLIC KEY:')}`);
|
|
724
|
+
console.log(` ${c('dim', 'Others can verify your contributions using your public key:')}`);
|
|
725
|
+
console.log(` ${c('cyan', publicKeyHex)}\n`);
|
|
726
|
+
|
|
727
|
+
console.log(`${c('green', '✓ Successfully joined Edge-Net!')}\n`);
|
|
728
|
+
console.log(`${c('dim', 'Press Ctrl+C to disconnect.')}\n`);
|
|
729
|
+
|
|
730
|
+
// Keep running with periodic status updates
|
|
731
|
+
let ticks = 0;
|
|
732
|
+
const statusInterval = setInterval(() => {
|
|
733
|
+
ticks++;
|
|
734
|
+
const motivation = events.getMotivation(BigInt(ticks * 10));
|
|
735
|
+
if (ticks % 10 === 0) {
|
|
736
|
+
console.log(` ${c('dim', `[${ticks}s]`)} ${c('cyan', 'Contributing...')} ${motivation}`);
|
|
737
|
+
}
|
|
738
|
+
}, 1000);
|
|
739
|
+
|
|
740
|
+
process.on('SIGINT', () => {
|
|
741
|
+
clearInterval(statusInterval);
|
|
742
|
+
console.log(`\n${c('yellow', 'Disconnected from Edge-Net.')}`);
|
|
743
|
+
console.log(`${c('dim', 'Your identity is preserved. Rejoin anytime.')}\n`);
|
|
744
|
+
|
|
745
|
+
// Clean up WASM resources
|
|
746
|
+
detector.free();
|
|
747
|
+
dp.free();
|
|
748
|
+
model.free();
|
|
749
|
+
coherence.free();
|
|
750
|
+
evolution.free();
|
|
751
|
+
events.free();
|
|
752
|
+
piKey.free();
|
|
753
|
+
|
|
754
|
+
process.exit(0);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
async function showStatus(wasm, piKey) {
|
|
759
|
+
console.log(`${c('bold', 'CONTRIBUTOR STATUS:')}\n`);
|
|
760
|
+
|
|
761
|
+
const publicKey = piKey.getPublicKey();
|
|
762
|
+
const stats = JSON.parse(piKey.getStats());
|
|
763
|
+
|
|
764
|
+
console.log(` ${c('cyan', 'Identity:')} ${piKey.getShortId()}`);
|
|
765
|
+
console.log(` ${c('cyan', 'Public Key:')} ${toHex(publicKey).substring(0, 32)}...`);
|
|
766
|
+
console.log(` ${c('cyan', 'Pi Magic:')} ${piKey.verifyPiMagic() ? c('green', '✓') : c('red', '✗')}`);
|
|
767
|
+
|
|
768
|
+
// Create temp components to check status
|
|
769
|
+
const evolution = new wasm.EvolutionEngine();
|
|
770
|
+
const coherence = new wasm.CoherenceEngine();
|
|
771
|
+
|
|
772
|
+
console.log(`\n${c('bold', 'NETWORK METRICS:')}`);
|
|
773
|
+
console.log(` ${c('cyan', 'Fitness:')} ${evolution.getNetworkFitness().toFixed(4)}`);
|
|
774
|
+
console.log(` ${c('cyan', 'Merkle Root:')} ${coherence.getMerkleRoot().substring(0, 24)}...`);
|
|
775
|
+
console.log(` ${c('cyan', 'Conflicts:')} ${coherence.conflictCount()}`);
|
|
776
|
+
console.log(` ${c('cyan', 'Quarantined:')} ${coherence.quarantinedCount()}`);
|
|
777
|
+
console.log(` ${c('cyan', 'Events:')} ${coherence.eventCount()}\n`);
|
|
778
|
+
|
|
779
|
+
evolution.free();
|
|
780
|
+
coherence.free();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Show peers from network module
|
|
784
|
+
async function showPeers() {
|
|
785
|
+
console.log(`${c('bold', 'NETWORK PEERS:')}\n`);
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
const { promises: fs } = await import('fs');
|
|
789
|
+
const peersFile = join(homedir(), '.ruvector', 'network', 'peers.json');
|
|
790
|
+
|
|
791
|
+
if (!existsSync(peersFile)) {
|
|
792
|
+
console.log(` ${c('dim', 'No peers found. Join the network first.')}\n`);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const peers = JSON.parse(await fs.readFile(peersFile, 'utf-8'));
|
|
797
|
+
|
|
798
|
+
if (peers.length === 0) {
|
|
799
|
+
console.log(` ${c('dim', 'No peers discovered yet.')}\n`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
console.log(` ${c('cyan', 'Found')} ${peers.length} ${c('cyan', 'peers:')}\n`);
|
|
804
|
+
|
|
805
|
+
for (const peer of peers) {
|
|
806
|
+
const timeSince = Date.now() - peer.lastSeen;
|
|
807
|
+
const isActive = timeSince < 300000; // 5 minutes
|
|
808
|
+
const status = isActive ? c('green', '● Active') : c('dim', '○ Inactive');
|
|
809
|
+
|
|
810
|
+
console.log(` ${status} ${c('bold', peer.siteId)}`);
|
|
811
|
+
console.log(` ${c('dim', 'Pi-Key:')} π:${peer.piKey.slice(0, 12)}...`);
|
|
812
|
+
console.log(` ${c('dim', 'Public Key:')} ${peer.publicKey.slice(0, 16)}...`);
|
|
813
|
+
console.log(` ${c('dim', 'First Seen:')} ${new Date(peer.firstSeen).toLocaleString()}`);
|
|
814
|
+
console.log(` ${c('dim', 'Last Seen:')} ${new Date(peer.lastSeen).toLocaleString()}`);
|
|
815
|
+
console.log(` ${c('dim', 'Verified:')} ${peer.verified ? c('green', '✓ Yes') : c('yellow', '○ No')}`);
|
|
816
|
+
console.log('');
|
|
817
|
+
}
|
|
818
|
+
} catch (err) {
|
|
819
|
+
console.log(` ${c('red', '✗')} Error reading peers: ${err.message}\n`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Handle --networks command (list known networks)
|
|
824
|
+
async function handleListNetworks() {
|
|
825
|
+
console.log(`${c('bold', 'KNOWN NETWORKS:')}\n`);
|
|
826
|
+
|
|
827
|
+
try {
|
|
828
|
+
const registry = new NetworkRegistry();
|
|
829
|
+
await registry.load();
|
|
830
|
+
|
|
831
|
+
const networks = registry.listNetworks();
|
|
832
|
+
const active = registry.activeNetwork;
|
|
833
|
+
|
|
834
|
+
if (networks.length === 0) {
|
|
835
|
+
console.log(` ${c('dim', 'No networks registered.')}`);
|
|
836
|
+
console.log(` ${c('dim', 'Use --discover to find available networks.')}\n`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
for (const network of networks) {
|
|
841
|
+
const isActive = network.id === active;
|
|
842
|
+
const status = network.joined ?
|
|
843
|
+
(isActive ? c('green', '● Active') : c('cyan', '○ Joined')) :
|
|
844
|
+
c('dim', ' Available');
|
|
845
|
+
const typeIcon = network.type === 'public' ? '🌐' :
|
|
846
|
+
network.type === 'private' ? '🔒' : '🏢';
|
|
847
|
+
|
|
848
|
+
console.log(` ${status} ${typeIcon} ${c('bold', network.name)}`);
|
|
849
|
+
console.log(` ${c('dim', 'ID:')} ${network.id}`);
|
|
850
|
+
console.log(` ${c('dim', 'Type:')} ${network.type}`);
|
|
851
|
+
if (network.description) {
|
|
852
|
+
console.log(` ${c('dim', network.description)}`);
|
|
853
|
+
}
|
|
854
|
+
console.log('');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
console.log(`${c('dim', 'Use --switch <network-id> to change active network')}\n`);
|
|
858
|
+
|
|
859
|
+
} catch (err) {
|
|
860
|
+
console.log(` ${c('red', '✗')} Error: ${err.message}\n`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Handle --discover command
|
|
865
|
+
async function handleDiscoverNetworks() {
|
|
866
|
+
console.log(`${c('cyan', 'Discovering networks...')}\n`);
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
const manager = new MultiNetworkManager(null);
|
|
870
|
+
await manager.initialize();
|
|
871
|
+
const networks = await manager.discoverNetworks();
|
|
872
|
+
|
|
873
|
+
if (networks.length > 0) {
|
|
874
|
+
console.log(`\n${c('dim', 'To join a network:')} --network <id> [--invite <code>]`);
|
|
875
|
+
console.log(`${c('dim', 'To create your own:')} --create-network "Name" [--network-type private]\n`);
|
|
876
|
+
}
|
|
877
|
+
} catch (err) {
|
|
878
|
+
console.log(` ${c('red', '✗')} Error: ${err.message}\n`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Handle --create-network command
|
|
883
|
+
async function handleCreateNetwork(opts) {
|
|
884
|
+
console.log(`${c('cyan', 'Creating new network...')}\n`);
|
|
885
|
+
|
|
886
|
+
try {
|
|
887
|
+
const manager = new MultiNetworkManager(null);
|
|
888
|
+
await manager.initialize();
|
|
889
|
+
|
|
890
|
+
const result = await manager.createNetwork({
|
|
891
|
+
name: opts.createNetwork,
|
|
892
|
+
type: opts.networkType,
|
|
893
|
+
description: opts.networkDesc,
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
console.log(`\n${c('dim', 'To invite others (if private):')} Share the invite codes above`);
|
|
897
|
+
console.log(`${c('dim', 'To contribute:')} --network ${result.networkId}\n`);
|
|
898
|
+
|
|
899
|
+
} catch (err) {
|
|
900
|
+
console.log(` ${c('red', '✗')} Error: ${err.message}\n`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Handle --switch command
|
|
905
|
+
async function handleSwitchNetwork(networkId) {
|
|
906
|
+
console.log(`${c('cyan', `Switching to network ${networkId}...`)}\n`);
|
|
907
|
+
|
|
908
|
+
try {
|
|
909
|
+
const manager = new MultiNetworkManager(null);
|
|
910
|
+
await manager.initialize();
|
|
911
|
+
await manager.switchNetwork(networkId);
|
|
912
|
+
|
|
913
|
+
console.log(`\n${c('dim', 'Your contributions will now go to this network.')}\n`);
|
|
914
|
+
|
|
915
|
+
} catch (err) {
|
|
916
|
+
console.log(` ${c('red', '✗')} Error: ${err.message}\n`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Show network/QDAG statistics
|
|
921
|
+
async function showNetworkStats() {
|
|
922
|
+
console.log(`${c('bold', 'NETWORK STATISTICS:')}\n`);
|
|
923
|
+
|
|
924
|
+
try {
|
|
925
|
+
const { promises: fs } = await import('fs');
|
|
926
|
+
const qdagFile = join(homedir(), '.ruvector', 'network', 'qdag.json');
|
|
927
|
+
|
|
928
|
+
if (!existsSync(qdagFile)) {
|
|
929
|
+
console.log(` ${c('dim', 'No QDAG data found. Join the network first.')}\n`);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const qdag = JSON.parse(await fs.readFile(qdagFile, 'utf-8'));
|
|
934
|
+
|
|
935
|
+
const contributions = (qdag.nodes || []).filter(n => n.type === 'contribution');
|
|
936
|
+
const contributors = new Set(contributions.map(c => c.contributor));
|
|
937
|
+
const totalCredits = contributions.reduce((sum, c) => sum + (c.credits || 0), 0);
|
|
938
|
+
const totalCompute = contributions.reduce((sum, c) => sum + (c.computeUnits || 0), 0);
|
|
939
|
+
|
|
940
|
+
console.log(`${c('bold', 'QDAG Ledger:')}`);
|
|
941
|
+
console.log(` ${c('cyan', 'Total Nodes:')} ${qdag.nodes?.length || 0}`);
|
|
942
|
+
console.log(` ${c('cyan', 'Confirmed:')} ${qdag.confirmed?.length || 0}`);
|
|
943
|
+
console.log(` ${c('cyan', 'Current Tips:')} ${qdag.tips?.length || 0}`);
|
|
944
|
+
console.log('');
|
|
945
|
+
|
|
946
|
+
console.log(`${c('bold', 'Contributions:')}`);
|
|
947
|
+
console.log(` ${c('cyan', 'Total:')} ${contributions.length}`);
|
|
948
|
+
console.log(` ${c('cyan', 'Contributors:')} ${contributors.size}`);
|
|
949
|
+
console.log(` ${c('cyan', 'Total Credits:')} ${totalCredits}`);
|
|
950
|
+
console.log(` ${c('cyan', 'Compute Units:')} ${totalCompute.toLocaleString()}`);
|
|
951
|
+
console.log('');
|
|
952
|
+
|
|
953
|
+
// Show top contributors
|
|
954
|
+
if (contributors.size > 0) {
|
|
955
|
+
console.log(`${c('bold', 'Top Contributors:')}`);
|
|
956
|
+
const contributorStats = {};
|
|
957
|
+
for (const contrib of contributions) {
|
|
958
|
+
if (!contributorStats[contrib.contributor]) {
|
|
959
|
+
contributorStats[contrib.contributor] = { credits: 0, count: 0, siteId: contrib.siteId };
|
|
960
|
+
}
|
|
961
|
+
contributorStats[contrib.contributor].credits += contrib.credits || 0;
|
|
962
|
+
contributorStats[contrib.contributor].count++;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const sorted = Object.entries(contributorStats)
|
|
966
|
+
.sort((a, b) => b[1].credits - a[1].credits)
|
|
967
|
+
.slice(0, 5);
|
|
968
|
+
|
|
969
|
+
for (const [piKey, stats] of sorted) {
|
|
970
|
+
console.log(` ${c('green', '★')} ${stats.siteId || piKey.slice(0, 12)} - ${stats.credits} credits (${stats.count} contributions)`);
|
|
971
|
+
}
|
|
972
|
+
console.log('');
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Show recent activity
|
|
976
|
+
const recentContribs = contributions
|
|
977
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
978
|
+
.slice(0, 5);
|
|
979
|
+
|
|
980
|
+
if (recentContribs.length > 0) {
|
|
981
|
+
console.log(`${c('bold', 'Recent Activity:')}`);
|
|
982
|
+
for (const contrib of recentContribs) {
|
|
983
|
+
const time = new Date(contrib.timestamp).toLocaleTimeString();
|
|
984
|
+
console.log(` ${c('dim', time)} ${contrib.siteId || contrib.contributor.slice(0, 8)} +${contrib.credits} credits`);
|
|
985
|
+
}
|
|
986
|
+
console.log('');
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
} catch (err) {
|
|
990
|
+
console.log(` ${c('red', '✗')} Error reading network stats: ${err.message}\n`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Multi-contributor demonstration
|
|
995
|
+
async function demonstrateMultiContributor(wasm) {
|
|
996
|
+
console.log(`${c('bold', 'MULTI-CONTRIBUTOR DEMONSTRATION')}\n`);
|
|
997
|
+
console.log(`${c('dim', 'Simulating 3 contributors joining the network...')}\n`);
|
|
998
|
+
|
|
999
|
+
const contributors = [];
|
|
1000
|
+
|
|
1001
|
+
for (let i = 1; i <= 3; i++) {
|
|
1002
|
+
const piKey = new wasm.PiKey();
|
|
1003
|
+
const publicKey = piKey.getPublicKey();
|
|
1004
|
+
const shortId = piKey.getShortId();
|
|
1005
|
+
|
|
1006
|
+
contributors.push({ piKey, publicKey, shortId, id: i });
|
|
1007
|
+
|
|
1008
|
+
console.log(`${c('cyan', `Contributor ${i}:`)}`);
|
|
1009
|
+
console.log(` ${c('dim', 'Short ID:')} ${shortId}`);
|
|
1010
|
+
console.log(` ${c('dim', 'Public Key:')} ${toHex(publicKey).substring(0, 24)}...`);
|
|
1011
|
+
console.log(` ${c('dim', 'Pi Magic:')} ${piKey.verifyPiMagic() ? c('green', '✓') : c('red', '✗')}\n`);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Demonstrate cross-verification
|
|
1015
|
+
console.log(`${c('bold', 'CROSS-VERIFICATION TEST:')}\n`);
|
|
1016
|
+
|
|
1017
|
+
const testMessage = new TextEncoder().encode('Multi-contributor coordination test');
|
|
1018
|
+
|
|
1019
|
+
for (let i = 0; i < contributors.length; i++) {
|
|
1020
|
+
const signer = contributors[i];
|
|
1021
|
+
const signature = signer.piKey.sign(testMessage);
|
|
1022
|
+
|
|
1023
|
+
console.log(`${c('cyan', `Contributor ${signer.id} signs message:`)}`);
|
|
1024
|
+
|
|
1025
|
+
// Each other contributor verifies
|
|
1026
|
+
for (let j = 0; j < contributors.length; j++) {
|
|
1027
|
+
const verifier = contributors[j];
|
|
1028
|
+
const isValid = signer.piKey.verify(testMessage, signature, signer.publicKey);
|
|
1029
|
+
|
|
1030
|
+
if (i !== j) {
|
|
1031
|
+
console.log(` ${c('dim', `Contributor ${verifier.id} verifies:`)} ${isValid ? c('green', '✓ Valid') : c('red', '✗ Invalid')}`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
console.log('');
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Create shared coherence state
|
|
1038
|
+
const coherence = new wasm.CoherenceEngine();
|
|
1039
|
+
|
|
1040
|
+
console.log(`${c('bold', 'SHARED COHERENCE STATE:')}`);
|
|
1041
|
+
console.log(` ${c('cyan', 'Merkle Root:')} ${coherence.getMerkleRoot()}`);
|
|
1042
|
+
console.log(` ${c('cyan', 'Conflicts:')} ${coherence.conflictCount()}`);
|
|
1043
|
+
console.log(` ${c('cyan', 'Event Count:')} ${coherence.eventCount()}\n`);
|
|
1044
|
+
|
|
1045
|
+
console.log(`${c('green', '✓ Multi-contributor simulation complete!')}\n`);
|
|
1046
|
+
console.log(`${c('dim', 'All contributors can independently verify each other\'s signatures.')}`);
|
|
1047
|
+
console.log(`${c('dim', 'The coherence engine maintains consistent state across the network.')}\n`);
|
|
1048
|
+
|
|
1049
|
+
// Cleanup
|
|
1050
|
+
contributors.forEach(c => c.piKey.free());
|
|
1051
|
+
coherence.free();
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
async function main() {
|
|
1055
|
+
const args = process.argv.slice(2);
|
|
1056
|
+
|
|
1057
|
+
// Filter out 'join' if passed
|
|
1058
|
+
const filteredArgs = args.filter(a => a !== 'join');
|
|
1059
|
+
const opts = parseArgs(filteredArgs);
|
|
1060
|
+
|
|
1061
|
+
if (opts.help || args.includes('help') || args.includes('--help') || args.includes('-h')) {
|
|
1062
|
+
printHelp();
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Handle --list early (no WASM needed)
|
|
1067
|
+
if (opts.list) {
|
|
1068
|
+
printBanner();
|
|
1069
|
+
await listIdentities();
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Handle multi-network commands (no WASM needed)
|
|
1074
|
+
if (opts.listNetworks) {
|
|
1075
|
+
printBanner();
|
|
1076
|
+
await handleListNetworks();
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
if (opts.discoverNetworks) {
|
|
1081
|
+
printBanner();
|
|
1082
|
+
await handleDiscoverNetworks();
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (opts.createNetwork) {
|
|
1087
|
+
printBanner();
|
|
1088
|
+
await handleCreateNetwork(opts);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (opts.switchNetwork) {
|
|
1093
|
+
printBanner();
|
|
1094
|
+
await handleSwitchNetwork(opts.switchNetwork);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
printBanner();
|
|
1099
|
+
await setupPolyfills();
|
|
1100
|
+
|
|
1101
|
+
// Load WASM module
|
|
1102
|
+
const { createRequire } = await import('module');
|
|
1103
|
+
const require = createRequire(import.meta.url);
|
|
1104
|
+
|
|
1105
|
+
console.log(`${c('dim', 'Loading WASM module...')}`);
|
|
1106
|
+
const wasm = require('./node/ruvector_edge_net.cjs');
|
|
1107
|
+
console.log(`${c('green', '✓')} WASM module loaded\n`);
|
|
1108
|
+
|
|
1109
|
+
// Handle --history
|
|
1110
|
+
if (opts.history) {
|
|
1111
|
+
const password = opts.password || `${opts.site}-edge-net-key`;
|
|
1112
|
+
await showHistory(wasm, opts.site, password);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Handle --peers (show network peers)
|
|
1117
|
+
if (opts.peers) {
|
|
1118
|
+
await showPeers();
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Handle --network (show network/QDAG stats)
|
|
1123
|
+
if (args.includes('--network')) {
|
|
1124
|
+
await showNetworkStats();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
let piKey = null;
|
|
1129
|
+
let persistentIdentity = null;
|
|
1130
|
+
|
|
1131
|
+
try {
|
|
1132
|
+
// Handle different modes
|
|
1133
|
+
if (opts.export) {
|
|
1134
|
+
piKey = await exportIdentity(wasm, opts.export, opts.password);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (opts.import) {
|
|
1139
|
+
piKey = await importIdentity(wasm, opts.import, opts.password);
|
|
1140
|
+
} else if (opts.key) {
|
|
1141
|
+
// Join with existing public key (generate matching key for demo)
|
|
1142
|
+
console.log(`${c('cyan', 'Using provided public key...')}\n`);
|
|
1143
|
+
console.log(`${c('dim', 'Note: Full key management requires import/export.')}\n`);
|
|
1144
|
+
piKey = new wasm.PiKey();
|
|
1145
|
+
} else {
|
|
1146
|
+
// Use persistent identity (auto-creates or restores)
|
|
1147
|
+
const password = opts.password || `${opts.site}-edge-net-key`;
|
|
1148
|
+
persistentIdentity = new PersistentIdentity(opts.site, wasm);
|
|
1149
|
+
const result = await persistentIdentity.initialize(password);
|
|
1150
|
+
|
|
1151
|
+
if (result.isNew) {
|
|
1152
|
+
console.log(`${c('green', '✓')} New identity created: ${result.meta.shortId}`);
|
|
1153
|
+
console.log(` ${c('dim', 'Your identity is now stored locally and will persist.')}`);
|
|
1154
|
+
console.log(` ${c('dim', 'Storage:')} ${getIdentitiesDir()}\n`);
|
|
1155
|
+
} else {
|
|
1156
|
+
console.log(`${c('green', '✓')} Identity restored: ${result.meta.shortId}`);
|
|
1157
|
+
console.log(` ${c('dim', 'Member since:')} ${result.meta.createdAt}`);
|
|
1158
|
+
console.log(` ${c('dim', 'Total sessions:')} ${result.sessions}`);
|
|
1159
|
+
if (result.daysSinceLastSession !== null) {
|
|
1160
|
+
if (result.daysSinceLastSession > 30) {
|
|
1161
|
+
console.log(` ${c('yellow', 'Welcome back!')} ${result.daysSinceLastSession} days since last session`);
|
|
1162
|
+
} else if (result.daysSinceLastSession > 0) {
|
|
1163
|
+
console.log(` ${c('dim', 'Last session:')} ${result.daysSinceLastSession} days ago`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
console.log('');
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
piKey = persistentIdentity.piKey;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (opts.generate) {
|
|
1173
|
+
// Just generate, don't join
|
|
1174
|
+
console.log(`${c('green', '✓ Identity generated and persisted!')}\n`);
|
|
1175
|
+
console.log(`${c('dim', 'Your identity is stored at:')} ${getIdentitiesDir()}`);
|
|
1176
|
+
console.log(`${c('dim', 'Run again to continue with the same identity.')}\n`);
|
|
1177
|
+
|
|
1178
|
+
// Also demonstrate multi-contributor
|
|
1179
|
+
if (persistentIdentity) persistentIdentity.free();
|
|
1180
|
+
else if (piKey) piKey.free();
|
|
1181
|
+
await demonstrateMultiContributor(wasm);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
if (opts.status) {
|
|
1186
|
+
await showStatus(wasm, piKey);
|
|
1187
|
+
if (persistentIdentity) persistentIdentity.free();
|
|
1188
|
+
else if (piKey) piKey.free();
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Join the network with persistence
|
|
1193
|
+
if (persistentIdentity) {
|
|
1194
|
+
await joinNetworkPersistent(wasm, opts, persistentIdentity);
|
|
1195
|
+
} else {
|
|
1196
|
+
await joinNetwork(wasm, opts, piKey);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
console.error(`${c('red', '✗ Error:')} ${err.message}`);
|
|
1201
|
+
if (persistentIdentity) persistentIdentity.free();
|
|
1202
|
+
else if (piKey) piKey.free();
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Join network with persistent identity (tracks contributions)
|
|
1208
|
+
async function joinNetworkPersistent(wasm, opts, identity) {
|
|
1209
|
+
console.log(`${c('bold', 'JOINING EDGE-NET (Persistent Mode)...')}\n`);
|
|
1210
|
+
|
|
1211
|
+
const publicKeyHex = identity.meta.publicKey;
|
|
1212
|
+
|
|
1213
|
+
// Create components for network participation
|
|
1214
|
+
const detector = new wasm.ByzantineDetector(0.5);
|
|
1215
|
+
const dp = new wasm.DifferentialPrivacy(1.0, 0.001);
|
|
1216
|
+
const model = new wasm.FederatedModel(100, 0.01, 0.9);
|
|
1217
|
+
const coherence = new wasm.CoherenceEngine();
|
|
1218
|
+
const evolution = new wasm.EvolutionEngine();
|
|
1219
|
+
const events = new wasm.NetworkEvents();
|
|
1220
|
+
|
|
1221
|
+
// Initialize network manager for QDAG and peer discovery
|
|
1222
|
+
let networkManager = null;
|
|
1223
|
+
try {
|
|
1224
|
+
networkManager = new NetworkManager({
|
|
1225
|
+
piKey: identity.meta.shortId,
|
|
1226
|
+
publicKey: publicKeyHex,
|
|
1227
|
+
siteId: opts.site
|
|
1228
|
+
});
|
|
1229
|
+
await networkManager.initialize();
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
console.log(` ${c('yellow', '⚠')} Network module unavailable: ${err.message}`);
|
|
1232
|
+
console.log(` ${c('dim', 'Running in local mode (contributions recorded locally)')}\n`);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
console.log(`${c('bold', 'CONTRIBUTOR NODE:')}`);
|
|
1236
|
+
console.log(` ${c('cyan', 'Site ID:')} ${opts.site}`);
|
|
1237
|
+
console.log(` ${c('cyan', 'Short ID:')} ${identity.meta.shortId}`);
|
|
1238
|
+
console.log(` ${c('cyan', 'Public Key:')} ${publicKeyHex.substring(0, 16)}...${publicKeyHex.slice(-8)}`);
|
|
1239
|
+
console.log(` ${c('cyan', 'Member Since:')} ${new Date(identity.meta.createdAt).toLocaleDateString()}`);
|
|
1240
|
+
console.log(` ${c('cyan', 'Sessions:')} ${identity.meta.totalSessions}`);
|
|
1241
|
+
console.log(` ${c('cyan', 'Status:')} ${c('green', 'Connected')}`);
|
|
1242
|
+
console.log(` ${c('cyan', 'Mode:')} Persistent + QDAG\n`);
|
|
1243
|
+
|
|
1244
|
+
console.log(`${c('bold', 'ACTIVE COMPONENTS:')}`);
|
|
1245
|
+
console.log(` ${c('green', '✓')} Byzantine Detector (threshold=0.5)`);
|
|
1246
|
+
console.log(` ${c('green', '✓')} Differential Privacy (ε=1.0)`);
|
|
1247
|
+
console.log(` ${c('green', '✓')} Federated Model (dim=100)`);
|
|
1248
|
+
console.log(` ${c('green', '✓')} Coherence Engine`);
|
|
1249
|
+
console.log(` ${c('green', '✓')} Evolution Engine`);
|
|
1250
|
+
console.log(` ${c('green', '✓')} QDAG Ledger (contribution tracking)`);
|
|
1251
|
+
console.log(` ${c('green', '✓')} Peer Discovery (P2P network)`);
|
|
1252
|
+
|
|
1253
|
+
// Get themed status
|
|
1254
|
+
const themedStatus = events.getThemedStatus(1, BigInt(identity.meta.totalContributions || 0));
|
|
1255
|
+
console.log(`\n${c('bold', 'NETWORK STATUS:')}`);
|
|
1256
|
+
console.log(` ${themedStatus}`);
|
|
1257
|
+
|
|
1258
|
+
// Show network stats if available
|
|
1259
|
+
if (networkManager) {
|
|
1260
|
+
const netStats = networkManager.getNetworkStats();
|
|
1261
|
+
const myStats = networkManager.getMyStats();
|
|
1262
|
+
console.log(` ${c('cyan', 'QDAG Nodes:')} ${netStats.totalNodes}`);
|
|
1263
|
+
console.log(` ${c('cyan', 'Contributors:')} ${netStats.uniqueContributors}`);
|
|
1264
|
+
console.log(` ${c('cyan', 'Total Credits:')} ${netStats.totalCredits}`);
|
|
1265
|
+
console.log(` ${c('cyan', 'My Credits:')} ${myStats.totalCredits}`);
|
|
1266
|
+
}
|
|
1267
|
+
console.log('');
|
|
1268
|
+
|
|
1269
|
+
// Show persistence info
|
|
1270
|
+
console.log(`${c('bold', 'PERSISTENCE:')}`);
|
|
1271
|
+
console.log(` ${c('dim', 'Identity stored at:')} ${identity.identityPath}`);
|
|
1272
|
+
console.log(` ${c('dim', 'History stored at:')} ${identity.contributionPath}`);
|
|
1273
|
+
console.log(` ${c('dim', 'QDAG Ledger at:')} ~/.ruvector/network/qdag.json`);
|
|
1274
|
+
console.log(` ${c('dim', 'Your contributions are preserved across sessions (months/years).')}\n`);
|
|
1275
|
+
|
|
1276
|
+
console.log(`${c('green', '✓ Successfully joined Edge-Net!')}\n`);
|
|
1277
|
+
console.log(`${c('dim', 'Press Ctrl+C to disconnect.')}\n`);
|
|
1278
|
+
|
|
1279
|
+
// Keep running with periodic status updates and contribution tracking
|
|
1280
|
+
let ticks = 0;
|
|
1281
|
+
let contributions = 0;
|
|
1282
|
+
let totalCredits = 0;
|
|
1283
|
+
const statusInterval = setInterval(async () => {
|
|
1284
|
+
ticks++;
|
|
1285
|
+
|
|
1286
|
+
// Simulate contribution every 5 seconds
|
|
1287
|
+
if (ticks % 5 === 0) {
|
|
1288
|
+
contributions++;
|
|
1289
|
+
const computeUnits = Math.floor(Math.random() * 500) + 100;
|
|
1290
|
+
const credits = Math.floor(computeUnits / 100);
|
|
1291
|
+
totalCredits += credits;
|
|
1292
|
+
|
|
1293
|
+
// Record to local history
|
|
1294
|
+
identity.recordContribution('compute', { duration: 5, tick: ticks, computeUnits, credits });
|
|
1295
|
+
|
|
1296
|
+
// Record to QDAG ledger
|
|
1297
|
+
if (networkManager) {
|
|
1298
|
+
const taskId = `task-${Date.now().toString(36)}`;
|
|
1299
|
+
await networkManager.recordContribution(taskId, computeUnits);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const motivation = events.getMotivation(BigInt(ticks * 10));
|
|
1304
|
+
if (ticks % 10 === 0) {
|
|
1305
|
+
const peerCount = networkManager ? networkManager.getPeers().length : 0;
|
|
1306
|
+
console.log(` ${c('dim', `[${ticks}s]`)} ${c('cyan', 'Contributing...')} ${contributions} tasks | ${totalCredits} credits | ${peerCount} peers | ${motivation}`);
|
|
1307
|
+
}
|
|
1308
|
+
}, 1000);
|
|
1309
|
+
|
|
1310
|
+
process.on('SIGINT', () => {
|
|
1311
|
+
clearInterval(statusInterval);
|
|
1312
|
+
console.log(`\n${c('yellow', 'Disconnected from Edge-Net.')}`);
|
|
1313
|
+
console.log(`${c('green', '✓')} Session recorded: ${contributions} contributions, ${totalCredits} credits`);
|
|
1314
|
+
console.log(`${c('dim', 'Your identity, history, and QDAG records are preserved. Rejoin anytime.')}\n`);
|
|
1315
|
+
|
|
1316
|
+
// Clean up resources
|
|
1317
|
+
if (networkManager) networkManager.stop();
|
|
1318
|
+
detector.free();
|
|
1319
|
+
dp.free();
|
|
1320
|
+
model.free();
|
|
1321
|
+
coherence.free();
|
|
1322
|
+
evolution.free();
|
|
1323
|
+
events.free();
|
|
1324
|
+
identity.free();
|
|
1325
|
+
|
|
1326
|
+
process.exit(0);
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
main().catch(err => {
|
|
1331
|
+
console.error(`${colors.red}Fatal error: ${err.message}${colors.reset}`);
|
|
1332
|
+
process.exit(1);
|
|
1333
|
+
});
|