chainlesschain 0.51.0 → 0.81.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/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-Rvi759IS.js → AppLayout-6SPt_8Y_.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-DBhFxXYQ.js → Dashboard-Br7kCwKJ.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
- package/src/assets/web-panel/assets/{index-uL0cZ8N_.js → index-tN-8TosE.js} +2 -2
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/a2a.js +380 -0
- package/src/commands/agent-network.js +785 -0
- package/src/commands/automation.js +654 -0
- package/src/commands/bi.js +348 -0
- package/src/commands/crosschain.js +218 -0
- package/src/commands/dao.js +565 -0
- package/src/commands/did-v2.js +620 -0
- package/src/commands/dlp.js +341 -0
- package/src/commands/economy.js +578 -0
- package/src/commands/evolution.js +391 -0
- package/src/commands/evomap.js +394 -0
- package/src/commands/federation.js +283 -0
- package/src/commands/hmemory.js +442 -0
- package/src/commands/inference.js +318 -0
- package/src/commands/lowcode.js +356 -0
- package/src/commands/marketplace.js +256 -0
- package/src/commands/perf.js +433 -0
- package/src/commands/pipeline.js +449 -0
- package/src/commands/plugin-ecosystem.js +517 -0
- package/src/commands/privacy.js +321 -0
- package/src/commands/reputation.js +261 -0
- package/src/commands/sandbox.js +401 -0
- package/src/commands/siem.js +246 -0
- package/src/commands/sla.js +259 -0
- package/src/commands/social.js +311 -0
- package/src/commands/sso.js +798 -0
- package/src/commands/stress.js +230 -0
- package/src/commands/terraform.js +245 -0
- package/src/commands/workflow.js +320 -0
- package/src/commands/zkp.js +562 -1
- package/src/index.js +21 -0
- package/src/lib/a2a-protocol.js +451 -0
- package/src/lib/agent-economy.js +479 -0
- package/src/lib/agent-network.js +1121 -0
- package/src/lib/app-builder.js +239 -0
- package/src/lib/automation-engine.js +948 -0
- package/src/lib/bi-engine.js +338 -0
- package/src/lib/cross-chain.js +345 -0
- package/src/lib/dao-governance.js +569 -0
- package/src/lib/did-v2-manager.js +1127 -0
- package/src/lib/dlp-engine.js +389 -0
- package/src/lib/evolution-system.js +453 -0
- package/src/lib/evomap-federation.js +177 -0
- package/src/lib/evomap-governance.js +276 -0
- package/src/lib/federation-hardening.js +259 -0
- package/src/lib/hierarchical-memory.js +481 -0
- package/src/lib/inference-network.js +330 -0
- package/src/lib/perf-tuning.js +734 -0
- package/src/lib/pipeline-orchestrator.js +928 -0
- package/src/lib/plugin-ecosystem.js +1109 -0
- package/src/lib/privacy-computing.js +427 -0
- package/src/lib/reputation-optimizer.js +299 -0
- package/src/lib/sandbox-v2.js +306 -0
- package/src/lib/siem-exporter.js +333 -0
- package/src/lib/skill-marketplace.js +325 -0
- package/src/lib/sla-manager.js +275 -0
- package/src/lib/social-graph-analytics.js +707 -0
- package/src/lib/sso-manager.js +841 -0
- package/src/lib/stress-tester.js +330 -0
- package/src/lib/terraform-manager.js +363 -0
- package/src/lib/workflow-engine.js +454 -1
- package/src/lib/zkp-engine.js +523 -20
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
package/src/commands/sla.js
CHANGED
|
@@ -21,6 +21,25 @@ import {
|
|
|
21
21
|
generateReport,
|
|
22
22
|
SLA_TERMS,
|
|
23
23
|
VIOLATION_SEVERITY,
|
|
24
|
+
// V2
|
|
25
|
+
SLA_STATUS_V2,
|
|
26
|
+
SLA_TIER_V2,
|
|
27
|
+
SLA_TERM_V2,
|
|
28
|
+
VIOLATION_SEVERITY_V2,
|
|
29
|
+
VIOLATION_STATUS_V2,
|
|
30
|
+
SLA_DEFAULT_MAX_ACTIVE_PER_ORG,
|
|
31
|
+
setMaxActiveSlasPerOrg,
|
|
32
|
+
getMaxActiveSlasPerOrg,
|
|
33
|
+
getActiveSlaCountForOrg,
|
|
34
|
+
createSLAV2,
|
|
35
|
+
setSLAStatus,
|
|
36
|
+
expireSLA,
|
|
37
|
+
autoExpireSLAs,
|
|
38
|
+
setViolationStatus,
|
|
39
|
+
acknowledgeViolation,
|
|
40
|
+
resolveViolation,
|
|
41
|
+
waiveViolation,
|
|
42
|
+
getSLAStatsV2,
|
|
24
43
|
} from "../lib/sla-manager.js";
|
|
25
44
|
|
|
26
45
|
function _dbFromCtx(ctx) {
|
|
@@ -349,4 +368,244 @@ export function registerSlaCommand(program) {
|
|
|
349
368
|
process.exit(1);
|
|
350
369
|
}
|
|
351
370
|
});
|
|
371
|
+
|
|
372
|
+
// ---------- V2 (Phase 61) ----------
|
|
373
|
+
const withDb = async (fn) => {
|
|
374
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
375
|
+
if (!ctx.db) {
|
|
376
|
+
logger.error("Database not available");
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const db = ctx.db.getDatabase();
|
|
381
|
+
ensureSlaTables(db);
|
|
382
|
+
return await fn(db);
|
|
383
|
+
} finally {
|
|
384
|
+
await shutdown();
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
sla
|
|
389
|
+
.command("statuses")
|
|
390
|
+
.description("List SLA_STATUS_V2 values")
|
|
391
|
+
.action(() => {
|
|
392
|
+
console.log(JSON.stringify(Object.values(SLA_STATUS_V2), null, 2));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
sla
|
|
396
|
+
.command("tier-names")
|
|
397
|
+
.description("List SLA_TIER_V2 values")
|
|
398
|
+
.action(() => {
|
|
399
|
+
console.log(JSON.stringify(Object.values(SLA_TIER_V2), null, 2));
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
sla
|
|
403
|
+
.command("term-names")
|
|
404
|
+
.description("List SLA_TERM_V2 values")
|
|
405
|
+
.action(() => {
|
|
406
|
+
console.log(JSON.stringify(Object.values(SLA_TERM_V2), null, 2));
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
sla
|
|
410
|
+
.command("severities")
|
|
411
|
+
.description("List VIOLATION_SEVERITY_V2 values")
|
|
412
|
+
.action(() => {
|
|
413
|
+
console.log(
|
|
414
|
+
JSON.stringify(Object.values(VIOLATION_SEVERITY_V2), null, 2),
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
sla
|
|
419
|
+
.command("violation-statuses")
|
|
420
|
+
.description("List VIOLATION_STATUS_V2 values")
|
|
421
|
+
.action(() => {
|
|
422
|
+
console.log(JSON.stringify(Object.values(VIOLATION_STATUS_V2), null, 2));
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
sla
|
|
426
|
+
.command("default-max-active")
|
|
427
|
+
.description("Show SLA_DEFAULT_MAX_ACTIVE_PER_ORG")
|
|
428
|
+
.action(() => {
|
|
429
|
+
console.log(SLA_DEFAULT_MAX_ACTIVE_PER_ORG);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
sla
|
|
433
|
+
.command("max-active")
|
|
434
|
+
.description("Show current max active SLAs per org")
|
|
435
|
+
.action(() => {
|
|
436
|
+
console.log(getMaxActiveSlasPerOrg());
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
sla
|
|
440
|
+
.command("set-max-active <n>")
|
|
441
|
+
.description("Set per-org active-contract admission cap")
|
|
442
|
+
.action((n) => {
|
|
443
|
+
try {
|
|
444
|
+
const v = setMaxActiveSlasPerOrg(Number(n));
|
|
445
|
+
logger.success(`maxActiveSlasPerOrg=${v}`);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
logger.error(`Failed: ${err.message}`);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
sla
|
|
453
|
+
.command("active-count <org-id>")
|
|
454
|
+
.description("Show active SLA count for an org")
|
|
455
|
+
.action((orgId) => {
|
|
456
|
+
console.log(getActiveSlaCountForOrg(orgId));
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
sla
|
|
460
|
+
.command("create-v2 <org-id>")
|
|
461
|
+
.description("Create a V2 SLA contract (enforces per-org active cap)")
|
|
462
|
+
.option("-t, --tier <tier>", "gold|silver|bronze", "silver")
|
|
463
|
+
.option("-d, --duration <ms>", "Contract duration in ms", parseInt)
|
|
464
|
+
.option("-f, --fee <amount>", "Monthly fee", parseFloat)
|
|
465
|
+
.option("--json", "Output as JSON")
|
|
466
|
+
.action(async (orgId, options) => {
|
|
467
|
+
try {
|
|
468
|
+
await withDb((db) => {
|
|
469
|
+
const c = createSLAV2(db, {
|
|
470
|
+
orgId,
|
|
471
|
+
tier: options.tier,
|
|
472
|
+
duration: options.duration,
|
|
473
|
+
monthlyFee: options.fee,
|
|
474
|
+
});
|
|
475
|
+
if (options.json) {
|
|
476
|
+
console.log(JSON.stringify(c, null, 2));
|
|
477
|
+
} else {
|
|
478
|
+
logger.success(
|
|
479
|
+
`Created ${c.slaId.slice(0, 8)} [${c.tier}] → ${c.status}`,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
} catch (err) {
|
|
484
|
+
logger.error(`Failed: ${err.message}`);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
sla
|
|
490
|
+
.command("set-status <sla-id> <status>")
|
|
491
|
+
.description("Transition SLA to a given status (state-machine guarded)")
|
|
492
|
+
.action(async (slaId, status) => {
|
|
493
|
+
try {
|
|
494
|
+
await withDb((db) => {
|
|
495
|
+
const c = setSLAStatus(db, slaId, status);
|
|
496
|
+
logger.success(`${c.slaId.slice(0, 8)} → ${c.status}`);
|
|
497
|
+
});
|
|
498
|
+
} catch (err) {
|
|
499
|
+
logger.error(`Failed: ${err.message}`);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
sla
|
|
505
|
+
.command("expire <sla-id>")
|
|
506
|
+
.description("Expire an SLA (shortcut for set-status ... expired)")
|
|
507
|
+
.action(async (slaId) => {
|
|
508
|
+
try {
|
|
509
|
+
await withDb((db) => {
|
|
510
|
+
const c = expireSLA(db, slaId);
|
|
511
|
+
logger.success(`${c.slaId.slice(0, 8)} → ${c.status}`);
|
|
512
|
+
});
|
|
513
|
+
} catch (err) {
|
|
514
|
+
logger.error(`Failed: ${err.message}`);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
sla
|
|
520
|
+
.command("auto-expire")
|
|
521
|
+
.description("Bulk-flip ACTIVE contracts past endDate to EXPIRED")
|
|
522
|
+
.option("--json", "Output as JSON")
|
|
523
|
+
.action(async (options) => {
|
|
524
|
+
try {
|
|
525
|
+
await withDb((db) => {
|
|
526
|
+
const flipped = autoExpireSLAs(db);
|
|
527
|
+
if (options.json) {
|
|
528
|
+
console.log(JSON.stringify(flipped, null, 2));
|
|
529
|
+
} else {
|
|
530
|
+
logger.success(`Auto-expired ${flipped.length} contract(s)`);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
} catch (err) {
|
|
534
|
+
logger.error(`Failed: ${err.message}`);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
sla
|
|
540
|
+
.command("set-violation-status <violation-id> <status>")
|
|
541
|
+
.description("Transition a violation (open→{acknowledged,resolved,waived})")
|
|
542
|
+
.option("--note <note>", "Attach a note")
|
|
543
|
+
.action(async (violationId, status, options) => {
|
|
544
|
+
try {
|
|
545
|
+
await withDb((db) => {
|
|
546
|
+
const v = setViolationStatus(db, violationId, status, {
|
|
547
|
+
note: options.note,
|
|
548
|
+
});
|
|
549
|
+
logger.success(`${v.violationId.slice(0, 8)} → ${v.v2Status}`);
|
|
550
|
+
});
|
|
551
|
+
} catch (err) {
|
|
552
|
+
logger.error(`Failed: ${err.message}`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
sla
|
|
558
|
+
.command("acknowledge-violation <violation-id>")
|
|
559
|
+
.description("Acknowledge a violation")
|
|
560
|
+
.option("--note <note>", "Attach a note")
|
|
561
|
+
.action(async (violationId, options) => {
|
|
562
|
+
try {
|
|
563
|
+
await withDb((db) => {
|
|
564
|
+
const v = acknowledgeViolation(db, violationId, options.note);
|
|
565
|
+
logger.success(`${v.violationId.slice(0, 8)} → ${v.v2Status}`);
|
|
566
|
+
});
|
|
567
|
+
} catch (err) {
|
|
568
|
+
logger.error(`Failed: ${err.message}`);
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
sla
|
|
574
|
+
.command("resolve-violation <violation-id>")
|
|
575
|
+
.description("Resolve a violation")
|
|
576
|
+
.option("--note <note>", "Attach a note")
|
|
577
|
+
.action(async (violationId, options) => {
|
|
578
|
+
try {
|
|
579
|
+
await withDb((db) => {
|
|
580
|
+
const v = resolveViolation(db, violationId, options.note);
|
|
581
|
+
logger.success(`${v.violationId.slice(0, 8)} → ${v.v2Status}`);
|
|
582
|
+
});
|
|
583
|
+
} catch (err) {
|
|
584
|
+
logger.error(`Failed: ${err.message}`);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
sla
|
|
590
|
+
.command("waive-violation <violation-id>")
|
|
591
|
+
.description("Waive a violation")
|
|
592
|
+
.option("--note <note>", "Attach a note")
|
|
593
|
+
.action(async (violationId, options) => {
|
|
594
|
+
try {
|
|
595
|
+
await withDb((db) => {
|
|
596
|
+
const v = waiveViolation(db, violationId, options.note);
|
|
597
|
+
logger.success(`${v.violationId.slice(0, 8)} → ${v.v2Status}`);
|
|
598
|
+
});
|
|
599
|
+
} catch (err) {
|
|
600
|
+
logger.error(`Failed: ${err.message}`);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
sla
|
|
606
|
+
.command("stats-v2")
|
|
607
|
+
.description("Show aggregate V2 SLA stats (byStatus/byTier/violations)")
|
|
608
|
+
.action(() => {
|
|
609
|
+
console.log(JSON.stringify(getSLAStatsV2(), null, 2));
|
|
610
|
+
});
|
|
352
611
|
}
|
package/src/commands/social.js
CHANGED
|
@@ -35,6 +35,18 @@ import {
|
|
|
35
35
|
subscribe as graphSubscribe,
|
|
36
36
|
EDGE_TYPES,
|
|
37
37
|
} from "../lib/social-graph.js";
|
|
38
|
+
import {
|
|
39
|
+
METRICS as ANALYTICS_METRICS,
|
|
40
|
+
degreeCentrality,
|
|
41
|
+
closenessCentrality,
|
|
42
|
+
betweennessCentrality,
|
|
43
|
+
eigenvectorCentrality,
|
|
44
|
+
influenceScore,
|
|
45
|
+
detectCommunities,
|
|
46
|
+
shortestPath,
|
|
47
|
+
topByMetric,
|
|
48
|
+
analyticsStats,
|
|
49
|
+
} from "../lib/social-graph-analytics.js";
|
|
38
50
|
|
|
39
51
|
export function registerSocialCommand(program) {
|
|
40
52
|
const social = program
|
|
@@ -742,4 +754,303 @@ export function registerSocialCommand(program) {
|
|
|
742
754
|
process.exit(1);
|
|
743
755
|
}
|
|
744
756
|
});
|
|
757
|
+
|
|
758
|
+
// ── Graph analytics (Phase 42) ──────────────────────────────
|
|
759
|
+
|
|
760
|
+
async function _loadSnapshot(options) {
|
|
761
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
762
|
+
if (!ctx.db) {
|
|
763
|
+
logger.error("Database not available");
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
const db = ctx.db.getDatabase();
|
|
767
|
+
ensureGraphTables(db);
|
|
768
|
+
graphLoadFromDb(db);
|
|
769
|
+
return getGraphSnapshot({ edgeType: options.type });
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function _splitEdgeTypes(list) {
|
|
773
|
+
if (!list) return undefined;
|
|
774
|
+
return list
|
|
775
|
+
.split(",")
|
|
776
|
+
.map((s) => s.trim())
|
|
777
|
+
.filter(Boolean);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function _printScoreMap(scores, limit) {
|
|
781
|
+
const entries = Object.entries(scores).sort((a, b) => {
|
|
782
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
783
|
+
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
784
|
+
});
|
|
785
|
+
const slice = limit > 0 ? entries.slice(0, limit) : entries;
|
|
786
|
+
for (const [did, score] of slice) {
|
|
787
|
+
console.log(`${chalk.cyan(did.padEnd(28))} ${score.toFixed(6)}`);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
graph
|
|
792
|
+
.command("degree")
|
|
793
|
+
.description("Degree centrality per DID")
|
|
794
|
+
.option("-d, --direction <in|out|both>", "Edge direction", "both")
|
|
795
|
+
.option("--no-normalize", "Disable normalization by (n-1)")
|
|
796
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types to include")
|
|
797
|
+
.option("-l, --limit <n>", "Show top N", "10")
|
|
798
|
+
.option("--json", "Output as JSON")
|
|
799
|
+
.action(async (options) => {
|
|
800
|
+
try {
|
|
801
|
+
const snap = await _loadSnapshot(options);
|
|
802
|
+
const scores = degreeCentrality(snap, {
|
|
803
|
+
direction: options.direction,
|
|
804
|
+
normalize: options.normalize !== false,
|
|
805
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
806
|
+
});
|
|
807
|
+
if (options.json) console.log(JSON.stringify(scores, null, 2));
|
|
808
|
+
else _printScoreMap(scores, parseInt(options.limit, 10) || 10);
|
|
809
|
+
await shutdown();
|
|
810
|
+
} catch (err) {
|
|
811
|
+
logger.error(`Failed: ${err.message}`);
|
|
812
|
+
process.exit(1);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
graph
|
|
817
|
+
.command("closeness")
|
|
818
|
+
.description("Harmonic closeness centrality per DID")
|
|
819
|
+
.option("--directed", "Treat graph as directed")
|
|
820
|
+
.option("--no-normalize", "Disable normalization by (n-1)")
|
|
821
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
822
|
+
.option("-l, --limit <n>", "Show top N", "10")
|
|
823
|
+
.option("--json", "Output as JSON")
|
|
824
|
+
.action(async (options) => {
|
|
825
|
+
try {
|
|
826
|
+
const snap = await _loadSnapshot(options);
|
|
827
|
+
const scores = closenessCentrality(snap, {
|
|
828
|
+
directed: !!options.directed,
|
|
829
|
+
normalize: options.normalize !== false,
|
|
830
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
831
|
+
});
|
|
832
|
+
if (options.json) console.log(JSON.stringify(scores, null, 2));
|
|
833
|
+
else _printScoreMap(scores, parseInt(options.limit, 10) || 10);
|
|
834
|
+
await shutdown();
|
|
835
|
+
} catch (err) {
|
|
836
|
+
logger.error(`Failed: ${err.message}`);
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
graph
|
|
842
|
+
.command("betweenness")
|
|
843
|
+
.description("Betweenness centrality (Brandes' algorithm)")
|
|
844
|
+
.option("--directed", "Treat graph as directed")
|
|
845
|
+
.option("--no-normalize", "Disable normalization")
|
|
846
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
847
|
+
.option("-l, --limit <n>", "Show top N", "10")
|
|
848
|
+
.option("--json", "Output as JSON")
|
|
849
|
+
.action(async (options) => {
|
|
850
|
+
try {
|
|
851
|
+
const snap = await _loadSnapshot(options);
|
|
852
|
+
const scores = betweennessCentrality(snap, {
|
|
853
|
+
directed: !!options.directed,
|
|
854
|
+
normalize: options.normalize !== false,
|
|
855
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
856
|
+
});
|
|
857
|
+
if (options.json) console.log(JSON.stringify(scores, null, 2));
|
|
858
|
+
else _printScoreMap(scores, parseInt(options.limit, 10) || 10);
|
|
859
|
+
await shutdown();
|
|
860
|
+
} catch (err) {
|
|
861
|
+
logger.error(`Failed: ${err.message}`);
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
graph
|
|
867
|
+
.command("eigenvector")
|
|
868
|
+
.description("Eigenvector centrality via power iteration")
|
|
869
|
+
.option("--directed", "Treat graph as directed")
|
|
870
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
871
|
+
.option("--iterations <n>", "Max iterations", "100")
|
|
872
|
+
.option("--tolerance <f>", "Convergence tolerance", "1e-6")
|
|
873
|
+
.option("-l, --limit <n>", "Show top N", "10")
|
|
874
|
+
.option("--json", "Output as JSON")
|
|
875
|
+
.action(async (options) => {
|
|
876
|
+
try {
|
|
877
|
+
const snap = await _loadSnapshot(options);
|
|
878
|
+
const scores = eigenvectorCentrality(snap, {
|
|
879
|
+
directed: !!options.directed,
|
|
880
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
881
|
+
iterations: parseInt(options.iterations, 10) || 100,
|
|
882
|
+
tolerance: parseFloat(options.tolerance) || 1e-6,
|
|
883
|
+
});
|
|
884
|
+
if (options.json) console.log(JSON.stringify(scores, null, 2));
|
|
885
|
+
else _printScoreMap(scores, parseInt(options.limit, 10) || 10);
|
|
886
|
+
await shutdown();
|
|
887
|
+
} catch (err) {
|
|
888
|
+
logger.error(`Failed: ${err.message}`);
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
graph
|
|
894
|
+
.command("influence")
|
|
895
|
+
.description("Composite influence score (weighted sum of 4 centralities)")
|
|
896
|
+
.option("--directed", "Treat graph as directed")
|
|
897
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
898
|
+
.option("--w-degree <f>", "Weight for degree", "0.25")
|
|
899
|
+
.option("--w-closeness <f>", "Weight for closeness", "0.25")
|
|
900
|
+
.option("--w-betweenness <f>", "Weight for betweenness", "0.25")
|
|
901
|
+
.option("--w-eigenvector <f>", "Weight for eigenvector", "0.25")
|
|
902
|
+
.option("-l, --limit <n>", "Show top N", "10")
|
|
903
|
+
.option("--json", "Output as JSON")
|
|
904
|
+
.action(async (options) => {
|
|
905
|
+
try {
|
|
906
|
+
const snap = await _loadSnapshot(options);
|
|
907
|
+
const scores = influenceScore(snap, {
|
|
908
|
+
directed: !!options.directed,
|
|
909
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
910
|
+
weights: {
|
|
911
|
+
degree: parseFloat(options.wDegree),
|
|
912
|
+
closeness: parseFloat(options.wCloseness),
|
|
913
|
+
betweenness: parseFloat(options.wBetweenness),
|
|
914
|
+
eigenvector: parseFloat(options.wEigenvector),
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
if (options.json) console.log(JSON.stringify(scores, null, 2));
|
|
918
|
+
else _printScoreMap(scores, parseInt(options.limit, 10) || 10);
|
|
919
|
+
await shutdown();
|
|
920
|
+
} catch (err) {
|
|
921
|
+
logger.error(`Failed: ${err.message}`);
|
|
922
|
+
process.exit(1);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
graph
|
|
927
|
+
.command("communities")
|
|
928
|
+
.description("Label-propagation community detection")
|
|
929
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
930
|
+
.option("--max-iterations <n>", "Max propagation rounds", "20")
|
|
931
|
+
.option("--min-size <n>", "Drop communities smaller than this", "1")
|
|
932
|
+
.option("--json", "Output as JSON")
|
|
933
|
+
.action(async (options) => {
|
|
934
|
+
try {
|
|
935
|
+
const snap = await _loadSnapshot(options);
|
|
936
|
+
const result = detectCommunities(snap, {
|
|
937
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
938
|
+
maxIterations: parseInt(options.maxIterations, 10) || 20,
|
|
939
|
+
minSize: parseInt(options.minSize, 10) || 1,
|
|
940
|
+
});
|
|
941
|
+
if (options.json) {
|
|
942
|
+
console.log(JSON.stringify(result, null, 2));
|
|
943
|
+
} else {
|
|
944
|
+
console.log(
|
|
945
|
+
chalk.bold(
|
|
946
|
+
`Communities: ${result.communities.length} modularity: ${result.modularity.toFixed(4)}`,
|
|
947
|
+
),
|
|
948
|
+
);
|
|
949
|
+
for (const c of result.communities) {
|
|
950
|
+
console.log(
|
|
951
|
+
` ${chalk.cyan(c.id)} (size ${c.size}) ${c.members.join(", ")}`,
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
await shutdown();
|
|
956
|
+
} catch (err) {
|
|
957
|
+
logger.error(`Failed: ${err.message}`);
|
|
958
|
+
process.exit(1);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
graph
|
|
963
|
+
.command("path <source> <target>")
|
|
964
|
+
.description("Shortest path between two DIDs (unweighted BFS)")
|
|
965
|
+
.option("--undirected", "Treat graph as undirected (default: directed)")
|
|
966
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
967
|
+
.option("--json", "Output as JSON")
|
|
968
|
+
.action(async (source, target, options) => {
|
|
969
|
+
try {
|
|
970
|
+
const snap = await _loadSnapshot(options);
|
|
971
|
+
const result = shortestPath(snap, source, target, {
|
|
972
|
+
directed: !options.undirected,
|
|
973
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
974
|
+
});
|
|
975
|
+
if (options.json) {
|
|
976
|
+
console.log(JSON.stringify(result, null, 2));
|
|
977
|
+
} else if (!result.found) {
|
|
978
|
+
console.log(chalk.yellow("No path found"));
|
|
979
|
+
} else {
|
|
980
|
+
console.log(
|
|
981
|
+
`${chalk.bold("distance")}: ${result.distance} ${result.path.join(" → ")}`,
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
await shutdown();
|
|
985
|
+
} catch (err) {
|
|
986
|
+
logger.error(`Failed: ${err.message}`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
graph
|
|
992
|
+
.command("top <metric>")
|
|
993
|
+
.description(`Top-N DIDs by metric (${ANALYTICS_METRICS.join("|")})`)
|
|
994
|
+
.option("--directed", "Treat graph as directed (where applicable)")
|
|
995
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
996
|
+
.option("-l, --limit <n>", "Limit", "10")
|
|
997
|
+
.option("--json", "Output as JSON")
|
|
998
|
+
.action(async (metric, options) => {
|
|
999
|
+
try {
|
|
1000
|
+
const snap = await _loadSnapshot(options);
|
|
1001
|
+
const rows = topByMetric(snap, metric, {
|
|
1002
|
+
directed: !!options.directed,
|
|
1003
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
1004
|
+
limit: parseInt(options.limit, 10) || 10,
|
|
1005
|
+
});
|
|
1006
|
+
if (options.json) {
|
|
1007
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
1008
|
+
} else {
|
|
1009
|
+
for (const r of rows) {
|
|
1010
|
+
console.log(
|
|
1011
|
+
`${chalk.cyan(r.did.padEnd(28))} ${r.score.toFixed(6)}`,
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
await shutdown();
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
logger.error(`Failed: ${err.message}`);
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
graph
|
|
1023
|
+
.command("analytics-stats")
|
|
1024
|
+
.description("Graph-wide analytics rollup (counts, density, top influence)")
|
|
1025
|
+
.option("-e, --edge-types <list>", "Comma-separated edge types")
|
|
1026
|
+
.option("--json", "Output as JSON")
|
|
1027
|
+
.action(async (options) => {
|
|
1028
|
+
try {
|
|
1029
|
+
const snap = await _loadSnapshot(options);
|
|
1030
|
+
const stats = analyticsStats(snap, {
|
|
1031
|
+
edgeTypes: _splitEdgeTypes(options.edgeTypes),
|
|
1032
|
+
});
|
|
1033
|
+
if (options.json) {
|
|
1034
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
1035
|
+
} else {
|
|
1036
|
+
console.log(chalk.bold("Social Graph Analytics"));
|
|
1037
|
+
console.log(` Nodes: ${stats.nodeCount}`);
|
|
1038
|
+
console.log(` Edges: ${stats.edgeCount}`);
|
|
1039
|
+
console.log(` Density: ${stats.density.toFixed(4)}`);
|
|
1040
|
+
console.log(` Generated: ${stats.generatedAt}`);
|
|
1041
|
+
if (stats.topInfluence.length > 0) {
|
|
1042
|
+
console.log(chalk.bold("\nTop influence:"));
|
|
1043
|
+
for (const r of stats.topInfluence) {
|
|
1044
|
+
console.log(
|
|
1045
|
+
` ${chalk.cyan(r.did.padEnd(28))} ${r.score.toFixed(6)}`,
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
await shutdown();
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
logger.error(`Failed: ${err.message}`);
|
|
1053
|
+
process.exit(1);
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
745
1056
|
}
|