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.
Files changed (70) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AppLayout-Rvi759IS.js → AppLayout-6SPt_8Y_.js} +1 -1
  4. package/src/assets/web-panel/assets/{Dashboard-DBhFxXYQ.js → Dashboard-Br7kCwKJ.js} +2 -2
  5. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  6. package/src/assets/web-panel/assets/{index-uL0cZ8N_.js → index-tN-8TosE.js} +2 -2
  7. package/src/assets/web-panel/index.html +2 -2
  8. package/src/commands/a2a.js +380 -0
  9. package/src/commands/agent-network.js +785 -0
  10. package/src/commands/automation.js +654 -0
  11. package/src/commands/bi.js +348 -0
  12. package/src/commands/crosschain.js +218 -0
  13. package/src/commands/dao.js +565 -0
  14. package/src/commands/did-v2.js +620 -0
  15. package/src/commands/dlp.js +341 -0
  16. package/src/commands/economy.js +578 -0
  17. package/src/commands/evolution.js +391 -0
  18. package/src/commands/evomap.js +394 -0
  19. package/src/commands/federation.js +283 -0
  20. package/src/commands/hmemory.js +442 -0
  21. package/src/commands/inference.js +318 -0
  22. package/src/commands/lowcode.js +356 -0
  23. package/src/commands/marketplace.js +256 -0
  24. package/src/commands/perf.js +433 -0
  25. package/src/commands/pipeline.js +449 -0
  26. package/src/commands/plugin-ecosystem.js +517 -0
  27. package/src/commands/privacy.js +321 -0
  28. package/src/commands/reputation.js +261 -0
  29. package/src/commands/sandbox.js +401 -0
  30. package/src/commands/siem.js +246 -0
  31. package/src/commands/sla.js +259 -0
  32. package/src/commands/social.js +311 -0
  33. package/src/commands/sso.js +798 -0
  34. package/src/commands/stress.js +230 -0
  35. package/src/commands/terraform.js +245 -0
  36. package/src/commands/workflow.js +320 -0
  37. package/src/commands/zkp.js +562 -1
  38. package/src/index.js +21 -0
  39. package/src/lib/a2a-protocol.js +451 -0
  40. package/src/lib/agent-economy.js +479 -0
  41. package/src/lib/agent-network.js +1121 -0
  42. package/src/lib/app-builder.js +239 -0
  43. package/src/lib/automation-engine.js +948 -0
  44. package/src/lib/bi-engine.js +338 -0
  45. package/src/lib/cross-chain.js +345 -0
  46. package/src/lib/dao-governance.js +569 -0
  47. package/src/lib/did-v2-manager.js +1127 -0
  48. package/src/lib/dlp-engine.js +389 -0
  49. package/src/lib/evolution-system.js +453 -0
  50. package/src/lib/evomap-federation.js +177 -0
  51. package/src/lib/evomap-governance.js +276 -0
  52. package/src/lib/federation-hardening.js +259 -0
  53. package/src/lib/hierarchical-memory.js +481 -0
  54. package/src/lib/inference-network.js +330 -0
  55. package/src/lib/perf-tuning.js +734 -0
  56. package/src/lib/pipeline-orchestrator.js +928 -0
  57. package/src/lib/plugin-ecosystem.js +1109 -0
  58. package/src/lib/privacy-computing.js +427 -0
  59. package/src/lib/reputation-optimizer.js +299 -0
  60. package/src/lib/sandbox-v2.js +306 -0
  61. package/src/lib/siem-exporter.js +333 -0
  62. package/src/lib/skill-marketplace.js +325 -0
  63. package/src/lib/sla-manager.js +275 -0
  64. package/src/lib/social-graph-analytics.js +707 -0
  65. package/src/lib/sso-manager.js +841 -0
  66. package/src/lib/stress-tester.js +330 -0
  67. package/src/lib/terraform-manager.js +363 -0
  68. package/src/lib/workflow-engine.js +454 -1
  69. package/src/lib/zkp-engine.js +523 -20
  70. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
@@ -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
  }
@@ -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
  }