@timmeck/trading-brain 1.0.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 (247) hide show
  1. package/README.md +306 -0
  2. package/dist/api/server.d.ts +21 -0
  3. package/dist/api/server.js +157 -0
  4. package/dist/api/server.js.map +1 -0
  5. package/dist/cli/colors.d.ts +46 -0
  6. package/dist/cli/colors.js +70 -0
  7. package/dist/cli/colors.js.map +1 -0
  8. package/dist/cli/commands/config.d.ts +2 -0
  9. package/dist/cli/commands/config.js +70 -0
  10. package/dist/cli/commands/config.js.map +1 -0
  11. package/dist/cli/commands/doctor.d.ts +2 -0
  12. package/dist/cli/commands/doctor.js +61 -0
  13. package/dist/cli/commands/doctor.js.map +1 -0
  14. package/dist/cli/commands/export.d.ts +2 -0
  15. package/dist/cli/commands/export.js +23 -0
  16. package/dist/cli/commands/export.js.map +1 -0
  17. package/dist/cli/commands/import.d.ts +2 -0
  18. package/dist/cli/commands/import.js +42 -0
  19. package/dist/cli/commands/import.js.map +1 -0
  20. package/dist/cli/commands/insights.d.ts +2 -0
  21. package/dist/cli/commands/insights.js +29 -0
  22. package/dist/cli/commands/insights.js.map +1 -0
  23. package/dist/cli/commands/network.d.ts +2 -0
  24. package/dist/cli/commands/network.js +43 -0
  25. package/dist/cli/commands/network.js.map +1 -0
  26. package/dist/cli/commands/query.d.ts +2 -0
  27. package/dist/cli/commands/query.js +27 -0
  28. package/dist/cli/commands/query.js.map +1 -0
  29. package/dist/cli/commands/rules.d.ts +2 -0
  30. package/dist/cli/commands/rules.js +26 -0
  31. package/dist/cli/commands/rules.js.map +1 -0
  32. package/dist/cli/commands/start.d.ts +2 -0
  33. package/dist/cli/commands/start.js +86 -0
  34. package/dist/cli/commands/start.js.map +1 -0
  35. package/dist/cli/commands/status.d.ts +2 -0
  36. package/dist/cli/commands/status.js +58 -0
  37. package/dist/cli/commands/status.js.map +1 -0
  38. package/dist/cli/commands/stop.d.ts +2 -0
  39. package/dist/cli/commands/stop.js +34 -0
  40. package/dist/cli/commands/stop.js.map +1 -0
  41. package/dist/cli/ipc-helper.d.ts +2 -0
  42. package/dist/cli/ipc-helper.js +26 -0
  43. package/dist/cli/ipc-helper.js.map +1 -0
  44. package/dist/config.d.ts +2 -0
  45. package/dist/config.js +107 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/db/connection.d.ts +2 -0
  48. package/dist/db/connection.js +19 -0
  49. package/dist/db/connection.js.map +1 -0
  50. package/dist/db/migrations/001_core.d.ts +2 -0
  51. package/dist/db/migrations/001_core.js +42 -0
  52. package/dist/db/migrations/001_core.js.map +1 -0
  53. package/dist/db/migrations/002_synapses.d.ts +2 -0
  54. package/dist/db/migrations/002_synapses.js +43 -0
  55. package/dist/db/migrations/002_synapses.js.map +1 -0
  56. package/dist/db/migrations/003_learning.d.ts +2 -0
  57. package/dist/db/migrations/003_learning.js +48 -0
  58. package/dist/db/migrations/003_learning.js.map +1 -0
  59. package/dist/db/migrations/004_research.d.ts +2 -0
  60. package/dist/db/migrations/004_research.js +29 -0
  61. package/dist/db/migrations/004_research.js.map +1 -0
  62. package/dist/db/migrations/index.d.ts +2 -0
  63. package/dist/db/migrations/index.js +45 -0
  64. package/dist/db/migrations/index.js.map +1 -0
  65. package/dist/db/repositories/calibration.repository.d.ts +25 -0
  66. package/dist/db/repositories/calibration.repository.js +66 -0
  67. package/dist/db/repositories/calibration.repository.js.map +1 -0
  68. package/dist/db/repositories/chain.repository.d.ts +28 -0
  69. package/dist/db/repositories/chain.repository.js +52 -0
  70. package/dist/db/repositories/chain.repository.js.map +1 -0
  71. package/dist/db/repositories/graph.repository.d.ts +33 -0
  72. package/dist/db/repositories/graph.repository.js +73 -0
  73. package/dist/db/repositories/graph.repository.js.map +1 -0
  74. package/dist/db/repositories/insight.repository.d.ts +30 -0
  75. package/dist/db/repositories/insight.repository.js +60 -0
  76. package/dist/db/repositories/insight.repository.js.map +1 -0
  77. package/dist/db/repositories/rule.repository.d.ts +35 -0
  78. package/dist/db/repositories/rule.repository.js +48 -0
  79. package/dist/db/repositories/rule.repository.js.map +1 -0
  80. package/dist/db/repositories/signal.repository.d.ts +17 -0
  81. package/dist/db/repositories/signal.repository.js +35 -0
  82. package/dist/db/repositories/signal.repository.js.map +1 -0
  83. package/dist/db/repositories/synapse.repository.d.ts +25 -0
  84. package/dist/db/repositories/synapse.repository.js +50 -0
  85. package/dist/db/repositories/synapse.repository.js.map +1 -0
  86. package/dist/db/repositories/trade.repository.d.ts +36 -0
  87. package/dist/db/repositories/trade.repository.js +64 -0
  88. package/dist/db/repositories/trade.repository.js.map +1 -0
  89. package/dist/graph/weighted-graph.d.ts +58 -0
  90. package/dist/graph/weighted-graph.js +149 -0
  91. package/dist/graph/weighted-graph.js.map +1 -0
  92. package/dist/index.d.ts +2 -0
  93. package/dist/index.js +49 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/ipc/client.d.ts +16 -0
  96. package/dist/ipc/client.js +95 -0
  97. package/dist/ipc/client.js.map +1 -0
  98. package/dist/ipc/protocol.d.ts +8 -0
  99. package/dist/ipc/protocol.js +29 -0
  100. package/dist/ipc/protocol.js.map +1 -0
  101. package/dist/ipc/router.d.ts +32 -0
  102. package/dist/ipc/router.js +70 -0
  103. package/dist/ipc/router.js.map +1 -0
  104. package/dist/ipc/server.d.ts +18 -0
  105. package/dist/ipc/server.js +142 -0
  106. package/dist/ipc/server.js.map +1 -0
  107. package/dist/learning/calibrator.d.ts +6 -0
  108. package/dist/learning/calibrator.js +57 -0
  109. package/dist/learning/calibrator.js.map +1 -0
  110. package/dist/learning/chain-detector.d.ts +17 -0
  111. package/dist/learning/chain-detector.js +29 -0
  112. package/dist/learning/chain-detector.js.map +1 -0
  113. package/dist/learning/learning-engine.d.ts +31 -0
  114. package/dist/learning/learning-engine.js +85 -0
  115. package/dist/learning/learning-engine.js.map +1 -0
  116. package/dist/learning/pattern-extractor.d.ts +14 -0
  117. package/dist/learning/pattern-extractor.js +40 -0
  118. package/dist/learning/pattern-extractor.js.map +1 -0
  119. package/dist/mcp/http-server.d.ts +14 -0
  120. package/dist/mcp/http-server.js +117 -0
  121. package/dist/mcp/http-server.js.map +1 -0
  122. package/dist/mcp/server.d.ts +1 -0
  123. package/dist/mcp/server.js +67 -0
  124. package/dist/mcp/server.js.map +1 -0
  125. package/dist/mcp/tools.d.ts +7 -0
  126. package/dist/mcp/tools.js +158 -0
  127. package/dist/mcp/tools.js.map +1 -0
  128. package/dist/research/research-engine.d.ts +21 -0
  129. package/dist/research/research-engine.js +204 -0
  130. package/dist/research/research-engine.js.map +1 -0
  131. package/dist/services/analytics.service.d.ts +16 -0
  132. package/dist/services/analytics.service.js +64 -0
  133. package/dist/services/analytics.service.js.map +1 -0
  134. package/dist/services/insight.service.d.ts +11 -0
  135. package/dist/services/insight.service.js +25 -0
  136. package/dist/services/insight.service.js.map +1 -0
  137. package/dist/services/signal.service.d.ts +22 -0
  138. package/dist/services/signal.service.js +96 -0
  139. package/dist/services/signal.service.js.map +1 -0
  140. package/dist/services/strategy.service.d.ts +29 -0
  141. package/dist/services/strategy.service.js +115 -0
  142. package/dist/services/strategy.service.js.map +1 -0
  143. package/dist/services/synapse.service.d.ts +20 -0
  144. package/dist/services/synapse.service.js +48 -0
  145. package/dist/services/synapse.service.js.map +1 -0
  146. package/dist/services/trade.service.d.ts +37 -0
  147. package/dist/services/trade.service.js +114 -0
  148. package/dist/services/trade.service.js.map +1 -0
  149. package/dist/signals/fingerprint.d.ts +29 -0
  150. package/dist/signals/fingerprint.js +98 -0
  151. package/dist/signals/fingerprint.js.map +1 -0
  152. package/dist/signals/wilson-score.d.ts +10 -0
  153. package/dist/signals/wilson-score.js +19 -0
  154. package/dist/signals/wilson-score.js.map +1 -0
  155. package/dist/synapses/decay.d.ts +6 -0
  156. package/dist/synapses/decay.js +17 -0
  157. package/dist/synapses/decay.js.map +1 -0
  158. package/dist/synapses/hebbian.d.ts +11 -0
  159. package/dist/synapses/hebbian.js +21 -0
  160. package/dist/synapses/hebbian.js.map +1 -0
  161. package/dist/synapses/synapse-manager.d.ts +22 -0
  162. package/dist/synapses/synapse-manager.js +99 -0
  163. package/dist/synapses/synapse-manager.js.map +1 -0
  164. package/dist/trading-core.d.ts +17 -0
  165. package/dist/trading-core.js +235 -0
  166. package/dist/trading-core.js.map +1 -0
  167. package/dist/types/config.types.d.ts +52 -0
  168. package/dist/types/config.types.js +2 -0
  169. package/dist/types/config.types.js.map +1 -0
  170. package/dist/types/ipc.types.d.ts +11 -0
  171. package/dist/types/ipc.types.js +2 -0
  172. package/dist/types/ipc.types.js.map +1 -0
  173. package/dist/utils/events.d.ts +48 -0
  174. package/dist/utils/events.js +23 -0
  175. package/dist/utils/events.js.map +1 -0
  176. package/dist/utils/hash.d.ts +1 -0
  177. package/dist/utils/hash.js +5 -0
  178. package/dist/utils/hash.js.map +1 -0
  179. package/dist/utils/logger.d.ts +8 -0
  180. package/dist/utils/logger.js +39 -0
  181. package/dist/utils/logger.js.map +1 -0
  182. package/dist/utils/paths.d.ts +3 -0
  183. package/dist/utils/paths.js +18 -0
  184. package/dist/utils/paths.js.map +1 -0
  185. package/package.json +47 -0
  186. package/src/api/server.ts +160 -0
  187. package/src/cli/colors.ts +80 -0
  188. package/src/cli/commands/config.ts +76 -0
  189. package/src/cli/commands/doctor.ts +62 -0
  190. package/src/cli/commands/export.ts +24 -0
  191. package/src/cli/commands/import.ts +44 -0
  192. package/src/cli/commands/insights.ts +30 -0
  193. package/src/cli/commands/network.ts +43 -0
  194. package/src/cli/commands/query.ts +28 -0
  195. package/src/cli/commands/rules.ts +27 -0
  196. package/src/cli/commands/start.ts +93 -0
  197. package/src/cli/commands/status.ts +64 -0
  198. package/src/cli/commands/stop.ts +33 -0
  199. package/src/cli/ipc-helper.ts +22 -0
  200. package/src/config.ts +103 -0
  201. package/src/db/connection.ts +22 -0
  202. package/src/db/migrations/001_core.ts +43 -0
  203. package/src/db/migrations/002_synapses.ts +44 -0
  204. package/src/db/migrations/003_learning.ts +49 -0
  205. package/src/db/migrations/004_research.ts +30 -0
  206. package/src/db/migrations/index.ts +60 -0
  207. package/src/db/repositories/calibration.repository.ts +86 -0
  208. package/src/db/repositories/chain.repository.ts +70 -0
  209. package/src/db/repositories/graph.repository.ts +103 -0
  210. package/src/db/repositories/insight.repository.ts +80 -0
  211. package/src/db/repositories/rule.repository.ts +67 -0
  212. package/src/db/repositories/signal.repository.ts +48 -0
  213. package/src/db/repositories/synapse.repository.ts +71 -0
  214. package/src/db/repositories/trade.repository.ts +97 -0
  215. package/src/graph/weighted-graph.ts +194 -0
  216. package/src/index.ts +55 -0
  217. package/src/ipc/client.ts +112 -0
  218. package/src/ipc/protocol.ts +35 -0
  219. package/src/ipc/router.ts +113 -0
  220. package/src/ipc/server.ts +150 -0
  221. package/src/learning/calibrator.ts +57 -0
  222. package/src/learning/chain-detector.ts +43 -0
  223. package/src/learning/learning-engine.ts +94 -0
  224. package/src/learning/pattern-extractor.ts +53 -0
  225. package/src/mcp/http-server.ts +118 -0
  226. package/src/mcp/server.ts +72 -0
  227. package/src/mcp/tools.ts +256 -0
  228. package/src/research/research-engine.ts +223 -0
  229. package/src/services/analytics.service.ts +68 -0
  230. package/src/services/insight.service.ts +29 -0
  231. package/src/services/signal.service.ts +109 -0
  232. package/src/services/strategy.service.ts +130 -0
  233. package/src/services/synapse.service.ts +58 -0
  234. package/src/services/trade.service.ts +139 -0
  235. package/src/signals/fingerprint.ts +93 -0
  236. package/src/signals/wilson-score.ts +17 -0
  237. package/src/synapses/decay.ts +19 -0
  238. package/src/synapses/hebbian.ts +23 -0
  239. package/src/synapses/synapse-manager.ts +112 -0
  240. package/src/trading-core.ts +285 -0
  241. package/src/types/config.types.ts +60 -0
  242. package/src/types/ipc.types.ts +8 -0
  243. package/src/utils/events.ts +42 -0
  244. package/src/utils/hash.ts +5 -0
  245. package/src/utils/logger.ts +48 -0
  246. package/src/utils/paths.ts +19 -0
  247. package/tsconfig.json +18 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/utils/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,OAAO,aAAc,SAAQ,YAAY;IAC7C,IAAI,CAAkC,KAAQ,EAAE,IAA2B;QACzE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,EAAE,CAAkC,KAAQ,EAAE,QAA+C;QAC3F,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAkC,KAAQ,EAAE,QAA+C;QAC7F,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,GAAG,CAAkC,KAAQ,EAAE,QAA+C;QAC5F,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;CACF;AAED,IAAI,WAAW,GAAyB,IAAI,CAAC;AAE7C,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,IAAI,aAAa,EAAE,CAAC;IACpC,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function sha256(input: string): string;
@@ -0,0 +1,5 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function sha256(input) {
3
+ return createHash('sha256').update(input).digest('hex');
4
+ }
5
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/utils/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,8 @@
1
+ import winston from 'winston';
2
+ export declare function createLogger(opts?: {
3
+ level?: string;
4
+ file?: string;
5
+ maxSize?: number;
6
+ maxFiles?: number;
7
+ }): winston.Logger;
8
+ export declare function getLogger(): winston.Logger;
@@ -0,0 +1,39 @@
1
+ import winston from 'winston';
2
+ import path from 'node:path';
3
+ import { getDataDir } from './paths.js';
4
+ const { combine, timestamp, printf, colorize } = winston.format;
5
+ const logFormat = printf(({ level, message, timestamp, ...meta }) => {
6
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
7
+ return `${timestamp} [${level}]${metaStr} ${message}`;
8
+ });
9
+ let loggerInstance = null;
10
+ export function createLogger(opts) {
11
+ if (loggerInstance)
12
+ return loggerInstance;
13
+ const level = opts?.level ?? process.env['TRADING_BRAIN_LOG_LEVEL'] ?? 'info';
14
+ const logFile = opts?.file ?? path.join(getDataDir(), 'trading-brain.log');
15
+ const maxSize = opts?.maxSize ?? 10 * 1024 * 1024;
16
+ const maxFiles = opts?.maxFiles ?? 3;
17
+ const transports = [
18
+ new winston.transports.File({
19
+ filename: logFile,
20
+ maxsize: maxSize,
21
+ maxFiles,
22
+ format: combine(timestamp(), logFormat),
23
+ }),
24
+ ];
25
+ if (process.env['NODE_ENV'] !== 'production') {
26
+ transports.push(new winston.transports.Console({
27
+ format: combine(colorize(), timestamp(), logFormat),
28
+ }));
29
+ }
30
+ loggerInstance = winston.createLogger({ level, transports });
31
+ return loggerInstance;
32
+ }
33
+ export function getLogger() {
34
+ if (!loggerInstance) {
35
+ return createLogger();
36
+ }
37
+ return loggerInstance;
38
+ }
39
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;AAEhE,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;IAClE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,GAAG,SAAS,KAAK,KAAK,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;AACxD,CAAC,CAAC,CAAC;AAEH,IAAI,cAAc,GAA0B,IAAI,CAAC;AAEjD,MAAM,UAAU,YAAY,CAAC,IAA6E;IACxG,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAE1C,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,IAAI,MAAM,CAAC;IAC9E,MAAM,OAAO,GAAG,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,mBAAmB,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,IAAI,CAAC,CAAC;IAErC,MAAM,UAAU,GAAwB;QACtC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAC1B,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,OAAO;YAChB,QAAQ;YACR,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC;SACxC,CAAC;KACH,CAAC;IAEF,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY,EAAE,CAAC;QAC7C,UAAU,CAAC,IAAI,CACb,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAC7B,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,SAAS,CAAC;SACpD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7D,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,YAAY,EAAE,CAAC;IACxB,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function normalizePath(filePath: string): string;
2
+ export declare function getDataDir(): string;
3
+ export declare function getPipeName(name?: string): string;
@@ -0,0 +1,18 @@
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+ export function normalizePath(filePath) {
4
+ return filePath.replace(/\\/g, '/');
5
+ }
6
+ export function getDataDir() {
7
+ const envDir = process.env['TRADING_BRAIN_DATA_DIR'];
8
+ if (envDir)
9
+ return path.resolve(envDir);
10
+ return path.join(os.homedir(), '.trading-brain');
11
+ }
12
+ export function getPipeName(name = 'trading-brain') {
13
+ if (process.platform === 'win32') {
14
+ return `\\\\.\\pipe\\${name}`;
15
+ }
16
+ return path.join(os.tmpdir(), `${name}.sock`);
17
+ }
18
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACrD,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,gBAAgB,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,eAAe;IACxD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,gBAAgB,IAAI,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;AAChD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@timmeck/trading-brain",
3
+ "version": "1.0.0",
4
+ "description": "Adaptive trading intelligence system with Hebbian synapses, spreading activation, Wilson Score confidence, and adaptive calibration",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "trading": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "test": "vitest"
14
+ },
15
+ "keywords": [
16
+ "trading-brain",
17
+ "mcp",
18
+ "claude-code",
19
+ "hebbian-learning",
20
+ "spreading-activation",
21
+ "wilson-score",
22
+ "adaptive-calibration",
23
+ "signal-fingerprinting",
24
+ "pattern-extraction",
25
+ "trading-intelligence"
26
+ ],
27
+ "author": "Tim Mecklenburg",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/timmeck/trading-brain"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.0.0",
35
+ "better-sqlite3": "^11.7.0",
36
+ "chalk": "^5.6.2",
37
+ "commander": "^13.0.0",
38
+ "winston": "^3.17.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/better-sqlite3": "^7.6.12",
42
+ "@types/node": "^22.0.0",
43
+ "tsx": "^4.19.0",
44
+ "typescript": "^5.7.0",
45
+ "vitest": "^3.0.0"
46
+ }
47
+ }
@@ -0,0 +1,160 @@
1
+ import http from 'node:http';
2
+ import { getLogger } from '../utils/logger.js';
3
+ import { getEventBus } from '../utils/events.js';
4
+ import type { IpcRouter } from '../ipc/router.js';
5
+
6
+ export interface ApiServerOptions {
7
+ port: number;
8
+ router: IpcRouter;
9
+ apiKey?: string;
10
+ }
11
+
12
+ export class ApiServer {
13
+ private server: http.Server | null = null;
14
+ private logger = getLogger();
15
+ private sseClients: Set<http.ServerResponse> = new Set();
16
+ private statsTimer: ReturnType<typeof setInterval> | null = null;
17
+
18
+ constructor(private options: ApiServerOptions) {}
19
+
20
+ start(): void {
21
+ const { port, apiKey } = this.options;
22
+
23
+ this.server = http.createServer((req, res) => {
24
+ res.setHeader('Access-Control-Allow-Origin', '*');
25
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
26
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
27
+
28
+ if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
29
+
30
+ if (apiKey) {
31
+ const provided = (req.headers['x-api-key'] as string) ?? req.headers.authorization?.replace('Bearer ', '');
32
+ if (provided !== apiKey) {
33
+ this.json(res, 401, { error: 'Unauthorized' });
34
+ return;
35
+ }
36
+ }
37
+
38
+ this.handleRequest(req, res).catch((err) => {
39
+ this.logger.error('API error:', err);
40
+ this.json(res, 500, { error: err instanceof Error ? err.message : String(err) });
41
+ });
42
+ });
43
+
44
+ this.server.listen(port, () => {
45
+ this.logger.info(`REST API server started on http://localhost:${port}`);
46
+ });
47
+
48
+ this.setupSSE();
49
+ }
50
+
51
+ stop(): void {
52
+ if (this.statsTimer) { clearInterval(this.statsTimer); this.statsTimer = null; }
53
+ for (const client of this.sseClients) { try { client.end(); } catch { /* ignore */ } }
54
+ this.sseClients.clear();
55
+ this.server?.close();
56
+ this.server = null;
57
+ this.logger.info('REST API server stopped');
58
+ }
59
+
60
+ private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
61
+ const url = new URL(req.url ?? '/', 'http://localhost');
62
+ const pathname = url.pathname;
63
+ const method = req.method ?? 'GET';
64
+
65
+ if (pathname === '/api/v1/health') {
66
+ this.json(res, 200, { status: 'ok', timestamp: new Date().toISOString() });
67
+ return;
68
+ }
69
+
70
+ if (pathname === '/api/v1/events' && method === 'GET') {
71
+ res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
72
+ res.write('data: {"type":"connected"}\n\n');
73
+ this.sseClients.add(res);
74
+ req.on('close', () => this.sseClients.delete(res));
75
+ return;
76
+ }
77
+
78
+ if (pathname === '/api/v1/methods' && method === 'GET') {
79
+ const methods = this.options.router.listMethods();
80
+ this.json(res, 200, { methods, rpcEndpoint: '/api/v1/rpc' });
81
+ return;
82
+ }
83
+
84
+ if (pathname === '/api/v1/rpc' && method === 'POST') {
85
+ const body = await this.readBody(req);
86
+ if (!body) { this.json(res, 400, { error: 'Empty request body' }); return; }
87
+
88
+ const parsed = JSON.parse(body);
89
+
90
+ if (Array.isArray(parsed)) {
91
+ const results = parsed.map((call: { method: string; params?: unknown; id?: string | number }) => {
92
+ try {
93
+ const result = this.options.router.handle(call.method, call.params ?? {});
94
+ return { id: call.id, result };
95
+ } catch (err) {
96
+ return { id: call.id, error: err instanceof Error ? err.message : String(err) };
97
+ }
98
+ });
99
+ this.json(res, 200, results);
100
+ return;
101
+ }
102
+
103
+ if (!parsed.method) { this.json(res, 400, { error: 'Missing "method" field' }); return; }
104
+
105
+ try {
106
+ const result = this.options.router.handle(parsed.method, parsed.params ?? {});
107
+ this.json(res, 200, { result });
108
+ } catch (err) {
109
+ this.json(res, 400, { error: err instanceof Error ? err.message : String(err) });
110
+ }
111
+ return;
112
+ }
113
+
114
+ this.json(res, 404, { error: `No route for ${method} ${pathname}` });
115
+ }
116
+
117
+ private json(res: http.ServerResponse, status: number, data: unknown): void {
118
+ res.writeHead(status, { 'Content-Type': 'application/json' });
119
+ res.end(JSON.stringify(data));
120
+ }
121
+
122
+ private readBody(req: http.IncomingMessage): Promise<string> {
123
+ return new Promise((resolve, reject) => {
124
+ const chunks: Buffer[] = [];
125
+ req.on('data', (chunk: Buffer) => chunks.push(chunk));
126
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
127
+ req.on('error', reject);
128
+ });
129
+ }
130
+
131
+ private setupSSE(): void {
132
+ const bus = getEventBus();
133
+ const eventNames = [
134
+ 'trade:recorded', 'synapse:updated', 'rule:learned',
135
+ 'chain:detected', 'insight:created', 'calibration:updated',
136
+ ] as const;
137
+
138
+ for (const eventName of eventNames) {
139
+ bus.on(eventName, (data: unknown) => {
140
+ this.broadcastSSE({ type: 'event', event: eventName, data });
141
+ });
142
+ }
143
+
144
+ this.statsTimer = setInterval(() => {
145
+ if (this.sseClients.size > 0) {
146
+ try {
147
+ const summary = this.options.router.handle('analytics.summary', {});
148
+ this.broadcastSSE({ type: 'stats_update', stats: summary });
149
+ } catch { /* ignore */ }
150
+ }
151
+ }, 30_000);
152
+ }
153
+
154
+ private broadcastSSE(data: unknown): void {
155
+ const msg = `data: ${JSON.stringify(data)}\n\n`;
156
+ for (const client of this.sseClients) {
157
+ try { client.write(msg); } catch { this.sseClients.delete(client); }
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,80 @@
1
+ import chalk from 'chalk';
2
+
3
+ export const c = {
4
+ blue: chalk.hex('#5b9cff'),
5
+ purple: chalk.hex('#b47aff'),
6
+ cyan: chalk.hex('#47e5ff'),
7
+ green: chalk.hex('#3dffa0'),
8
+ red: chalk.hex('#ff5577'),
9
+ orange: chalk.hex('#ffb347'),
10
+ dim: chalk.hex('#8b8fb0'),
11
+ dimmer: chalk.hex('#4a4d6e'),
12
+
13
+ label: chalk.hex('#8b8fb0'),
14
+ value: chalk.white.bold,
15
+ heading: chalk.hex('#5b9cff').bold,
16
+ success: chalk.hex('#3dffa0').bold,
17
+ error: chalk.hex('#ff5577').bold,
18
+ warn: chalk.hex('#ffb347').bold,
19
+ info: chalk.hex('#47e5ff'),
20
+ };
21
+
22
+ export const icons = {
23
+ brain: '🧠',
24
+ chart: '📊',
25
+ check: '✓',
26
+ cross: '✗',
27
+ arrow: '→',
28
+ dot: '●',
29
+ bar: '█',
30
+ barLight: '░',
31
+ dash: '─',
32
+ pipe: '│',
33
+ star: '★',
34
+ bolt: '⚡',
35
+ search: '🔍',
36
+ gear: '⚙',
37
+ synapse: '🔗',
38
+ insight: '💡',
39
+ warn: '⚠',
40
+ error: '❌',
41
+ ok: '✅',
42
+ clock: '⏱',
43
+ trade: '💹',
44
+ rule: '📋',
45
+ chain: '⛓',
46
+ };
47
+
48
+ export function header(title: string, icon?: string): string {
49
+ const prefix = icon ? `${icon} ` : '';
50
+ const line = c.dimmer(icons.dash.repeat(40));
51
+ return `\n${line}\n${prefix}${c.heading(title)}\n${line}`;
52
+ }
53
+
54
+ export function keyValue(key: string, value: string | number, indent = 2): string {
55
+ const pad = ' '.repeat(indent);
56
+ return `${pad}${c.label(key + ':')} ${c.value(String(value))}`;
57
+ }
58
+
59
+ export function divider(width = 40): string {
60
+ return c.dimmer(icons.dash.repeat(width));
61
+ }
62
+
63
+ function stripAnsi(str: string): string {
64
+ // eslint-disable-next-line no-control-regex
65
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
66
+ }
67
+
68
+ export function table(rows: string[][], colWidths?: number[]): string {
69
+ if (rows.length === 0) return '';
70
+ const widths = colWidths ?? rows[0]!.map((_, i) =>
71
+ Math.max(...rows.map(r => stripAnsi(r[i] ?? '').length))
72
+ );
73
+ return rows.map(row =>
74
+ row.map((cell, i) => {
75
+ const stripped = stripAnsi(cell);
76
+ const pad = Math.max(0, (widths[i] ?? stripped.length) - stripped.length);
77
+ return cell + ' '.repeat(pad);
78
+ }).join(' ')
79
+ ).join('\n');
80
+ }
@@ -0,0 +1,76 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getDataDir } from '../../utils/paths.js';
5
+ import { loadConfig } from '../../config.js';
6
+ import { c, icons, header } from '../colors.js';
7
+
8
+ export function configCommand(): Command {
9
+ const cmd = new Command('config').description('Manage configuration');
10
+
11
+ cmd.command('show')
12
+ .description('Show current configuration')
13
+ .action(() => {
14
+ console.log(header('Configuration', icons.gear));
15
+ const config = loadConfig();
16
+ console.log(JSON.stringify(config, null, 2));
17
+ });
18
+
19
+ cmd.command('set')
20
+ .description('Set a config value')
21
+ .argument('<key>', 'Config key (dot notation, e.g. api.port)')
22
+ .argument('<value>', 'Config value')
23
+ .action((key, value) => {
24
+ const configPath = path.join(getDataDir(), 'config.json');
25
+ let config: Record<string, unknown> = {};
26
+ if (fs.existsSync(configPath)) {
27
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
28
+ }
29
+
30
+ const parts = key.split('.');
31
+ let obj = config;
32
+ for (let i = 0; i < parts.length - 1; i++) {
33
+ if (!obj[parts[i]!] || typeof obj[parts[i]!] !== 'object') {
34
+ obj[parts[i]!] = {};
35
+ }
36
+ obj = obj[parts[i]!] as Record<string, unknown>;
37
+ }
38
+
39
+ // Auto-convert numbers and booleans
40
+ let parsed: unknown = value;
41
+ if (value === 'true') parsed = true;
42
+ else if (value === 'false') parsed = false;
43
+ else if (!isNaN(Number(value))) parsed = Number(value);
44
+
45
+ obj[parts[parts.length - 1]!] = parsed;
46
+
47
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
48
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
49
+ console.log(`${icons.ok} ${c.success(`Set ${key} = ${value}`)}`);
50
+ });
51
+
52
+ cmd.command('delete')
53
+ .description('Delete a config key (revert to default)')
54
+ .argument('<key>', 'Config key to delete')
55
+ .action((key) => {
56
+ const configPath = path.join(getDataDir(), 'config.json');
57
+ if (!fs.existsSync(configPath)) {
58
+ console.log(`${c.dim('No config file found.')}`);
59
+ return;
60
+ }
61
+
62
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
63
+ const parts = key.split('.');
64
+ let obj = config;
65
+ for (let i = 0; i < parts.length - 1; i++) {
66
+ if (!obj[parts[i]!]) return;
67
+ obj = obj[parts[i]!];
68
+ }
69
+ delete obj[parts[parts.length - 1]!];
70
+
71
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
72
+ console.log(`${icons.ok} ${c.success(`Deleted ${key} (reverted to default)`)}`);
73
+ });
74
+
75
+ return cmd;
76
+ }
@@ -0,0 +1,62 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getDataDir, getPipeName } from '../../utils/paths.js';
5
+ import { c, icons, header } from '../colors.js';
6
+
7
+ export function doctorCommand(): Command {
8
+ return new Command('doctor')
9
+ .description('Health check for Trading Brain')
10
+ .action(async () => {
11
+ console.log(header('Trading Brain Doctor', icons.gear));
12
+ let issues = 0;
13
+
14
+ // Check data dir
15
+ const dataDir = getDataDir();
16
+ if (fs.existsSync(dataDir)) {
17
+ console.log(` ${icons.ok} Data dir exists: ${c.dim(dataDir)}`);
18
+ } else {
19
+ console.log(` ${icons.warn} Data dir missing: ${c.dim(dataDir)}`);
20
+ issues++;
21
+ }
22
+
23
+ // Check DB
24
+ const dbPath = path.join(dataDir, 'trading-brain.db');
25
+ if (fs.existsSync(dbPath)) {
26
+ const stat = fs.statSync(dbPath);
27
+ console.log(` ${icons.ok} Database exists: ${c.dim(`${(stat.size / 1024).toFixed(0)} KB`)}`);
28
+ } else {
29
+ console.log(` ${icons.warn} Database not yet created ${c.dim('(will be created on first start)')}`);
30
+ }
31
+
32
+ // Check PID
33
+ const pidPath = path.join(dataDir, 'trading-brain.pid');
34
+ if (fs.existsSync(pidPath)) {
35
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
36
+ try {
37
+ process.kill(pid, 0);
38
+ console.log(` ${icons.ok} Daemon running ${c.dim(`(PID ${pid})`)}`);
39
+ } catch {
40
+ console.log(` ${icons.warn} Stale PID file ${c.dim(`(PID ${pid} not running)`)}`);
41
+ issues++;
42
+ }
43
+ } else {
44
+ console.log(` ${icons.warn} Daemon not running`);
45
+ }
46
+
47
+ // Check pipe
48
+ const pipeName = getPipeName();
49
+ console.log(` ${c.dim('IPC:')} ${pipeName}`);
50
+
51
+ // Check ports
52
+ console.log(` ${c.dim('REST API:')} http://localhost:7779`);
53
+ console.log(` ${c.dim('MCP HTTP:')} http://localhost:7780`);
54
+
55
+ console.log();
56
+ if (issues === 0) {
57
+ console.log(` ${icons.ok} ${c.success('All checks passed!')}`);
58
+ } else {
59
+ console.log(` ${icons.warn} ${c.warn(`${issues} issue(s) found`)}`);
60
+ }
61
+ });
62
+ }
@@ -0,0 +1,24 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'node:fs';
3
+ import { withIpc } from '../ipc-helper.js';
4
+ import { c, icons } from '../colors.js';
5
+
6
+ export function exportCommand(): Command {
7
+ return new Command('export')
8
+ .description('Export all brain data as JSON')
9
+ .option('-o, --output <file>', 'Output file path', 'trading-brain-export.json')
10
+ .action(async (opts) => {
11
+ await withIpc(async (client) => {
12
+ const summary = await client.request('analytics.summary', {});
13
+ const rules = await client.request('rule.list', {});
14
+ const chains = await client.request('chain.list', { limit: 100 });
15
+ const insights = await client.request('insight.list', { limit: 100 });
16
+ const network = await client.request('synapse.stats', {});
17
+ const calibration = await client.request('calibration.get', {});
18
+
19
+ const data = { summary, rules, chains, insights, network, calibration, exportedAt: new Date().toISOString() };
20
+ fs.writeFileSync(opts.output, JSON.stringify(data, null, 2));
21
+ console.log(`${icons.ok} ${c.success(`Exported to ${opts.output}`)}`);
22
+ });
23
+ });
24
+ }
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'node:fs';
3
+ import { withIpc } from '../ipc-helper.js';
4
+ import { c, icons } from '../colors.js';
5
+
6
+ export function importCommand(): Command {
7
+ return new Command('import')
8
+ .description('Import trades from JSON file')
9
+ .argument('<file>', 'JSON file to import')
10
+ .action(async (file) => {
11
+ if (!fs.existsSync(file)) {
12
+ console.error(`${icons.error} ${c.error(`File not found: ${file}`)}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ const raw = fs.readFileSync(file, 'utf-8');
17
+ const data = JSON.parse(raw);
18
+
19
+ if (!Array.isArray(data)) {
20
+ console.error(`${icons.error} ${c.error('Expected JSON array of trade objects.')}`);
21
+ process.exit(1);
22
+ }
23
+
24
+ await withIpc(async (client) => {
25
+ let imported = 0;
26
+ for (const trade of data) {
27
+ try {
28
+ await client.request('trade.recordOutcome', {
29
+ signals: trade.signals ?? {},
30
+ regime: trade.regime,
31
+ profitPct: trade.profitPct ?? trade.profit_pct ?? 0,
32
+ win: trade.win ?? false,
33
+ botType: trade.botType ?? trade.bot_type ?? 'unknown',
34
+ pair: trade.pair ?? 'unknown',
35
+ });
36
+ imported++;
37
+ } catch (err) {
38
+ console.error(`${icons.warn} ${c.warn(`Failed to import trade: ${err}`)}`);
39
+ }
40
+ }
41
+ console.log(`${icons.ok} ${c.success(`Imported ${imported}/${data.length} trades`)}`);
42
+ });
43
+ });
44
+ }
@@ -0,0 +1,30 @@
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header } from '../colors.js';
4
+
5
+ export function insightsCommand(): Command {
6
+ return new Command('insights')
7
+ .description('Show research insights')
8
+ .option('-t, --type <type>', 'Filter by type (trend, gap, synergy, performance, regime_shift)')
9
+ .option('-l, --limit <n>', 'Max results', '10')
10
+ .action(async (opts) => {
11
+ console.log(header('Research Insights', icons.insight));
12
+
13
+ await withIpc(async (client) => {
14
+ const method = opts.type ? 'insight.byType' : 'insight.list';
15
+ const params = opts.type ? { type: opts.type } : { limit: Number(opts.limit) };
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ const results: any = await client.request(method, params);
18
+ if (!results?.length) {
19
+ console.log(` ${c.dim('No insights found.')}`);
20
+ return;
21
+ }
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ for (const ins of results) {
24
+ const sev = ins.severity === 'high' ? c.red(`[${ins.severity}]`) : ins.severity === 'medium' ? c.orange(`[${ins.severity}]`) : c.dim(`[${ins.severity}]`);
25
+ console.log(` ${sev} ${c.cyan(ins.type)} — ${c.value(ins.title)}`);
26
+ console.log(` ${c.dim(ins.description)}`);
27
+ }
28
+ });
29
+ });
30
+ }
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, keyValue } from '../colors.js';
4
+
5
+ export function networkCommand(): Command {
6
+ return new Command('network')
7
+ .description('Show synapse network overview')
8
+ .option('-n, --node <id>', 'Explore from specific node')
9
+ .action(async (opts) => {
10
+ console.log(header('Synapse Network', icons.synapse));
11
+
12
+ await withIpc(async (client) => {
13
+ if (opts.node) {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const activated: any = await client.request('synapse.explore', { query: opts.node });
16
+ if (!activated?.length) {
17
+ console.log(` ${c.dim(`No node found matching "${opts.node}"`)}`);
18
+ return;
19
+ }
20
+ console.log(` ${c.info('Spreading Activation from:')} ${c.value(opts.node)}\n`);
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ for (const node of activated.slice(0, 20)) {
23
+ const bar = c.cyan('█'.repeat(Math.round(node.activation * 20)));
24
+ console.log(` ${bar} ${c.dim(node.type)} ${c.value(node.label)} ${c.dim(`(${node.activation.toFixed(3)})`)}`);
25
+ }
26
+ } else {
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const stats: any = await client.request('synapse.stats', {});
29
+ console.log(keyValue('Synapses', stats.totalSynapses));
30
+ console.log(keyValue('Avg Weight', stats.avgWeight?.toFixed(3) ?? '0'));
31
+ console.log(keyValue('Graph Nodes', stats.graphNodes));
32
+ console.log(keyValue('Graph Edges', stats.graphEdges));
33
+ if (stats.strongest?.length > 0) {
34
+ console.log(`\n ${c.cyan.bold('Strongest Synapses:')}`);
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ for (const s of stats.strongest) {
37
+ console.log(` ${c.green(s.weight.toFixed(3))} ${c.dim(`(${s.activations}x)`)} ${c.value(s.id)}`);
38
+ }
39
+ }
40
+ }
41
+ });
42
+ });
43
+ }