@vizzor/cli 0.8.5 → 0.10.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/dist/index.js CHANGED
@@ -70,6 +70,10 @@ var init_schema = __esm({
70
70
  enableAuth: false,
71
71
  corsOrigin: "*"
72
72
  })),
73
+ n8n: z.object({
74
+ enabled: z.boolean().default(false),
75
+ webhookUrl: z.string().optional()
76
+ }).default(() => ({ enabled: false })),
73
77
  discordToken: z.string().optional(),
74
78
  discordGuildId: z.string().optional(),
75
79
  telegramToken: z.string().optional()
@@ -135,6 +139,30 @@ function loadConfig() {
135
139
  }
136
140
  raw["ai"]["provider"] = process.env["VIZZOR_AI_PROVIDER"];
137
141
  }
142
+ if (process.env["DATABASE_TYPE"] || process.env["DATABASE_URL"]) {
143
+ if (!raw["database"] || typeof raw["database"] !== "object") {
144
+ raw["database"] = {};
145
+ }
146
+ const db2 = raw["database"];
147
+ if (process.env["DATABASE_TYPE"]) db2["type"] = process.env["DATABASE_TYPE"];
148
+ if (process.env["DATABASE_URL"]) db2["url"] = process.env["DATABASE_URL"];
149
+ }
150
+ if (process.env["ML_ENABLED"] || process.env["ML_SIDECAR_URL"]) {
151
+ if (!raw["ml"] || typeof raw["ml"] !== "object") {
152
+ raw["ml"] = {};
153
+ }
154
+ const ml = raw["ml"];
155
+ if (process.env["ML_ENABLED"]) ml["enabled"] = process.env["ML_ENABLED"] === "true";
156
+ if (process.env["ML_SIDECAR_URL"]) ml["sidecarUrl"] = process.env["ML_SIDECAR_URL"];
157
+ }
158
+ if (process.env["API_PORT"] || process.env["API_HOST"]) {
159
+ if (!raw["api"] || typeof raw["api"] !== "object") {
160
+ raw["api"] = {};
161
+ }
162
+ const api = raw["api"];
163
+ if (process.env["API_PORT"]) api["port"] = Number(process.env["API_PORT"]);
164
+ if (process.env["API_HOST"]) api["host"] = process.env["API_HOST"];
165
+ }
138
166
  const config2 = vizzorConfigSchema.parse(raw);
139
167
  cachedConfig = config2;
140
168
  return config2;
@@ -172,7 +200,10 @@ function saveConfigValue(key, value) {
172
200
  if (!raw[section] || typeof raw[section] !== "object") {
173
201
  raw[section] = {};
174
202
  }
175
- const parsed = field === "maxTokens" ? Number(value) : value;
203
+ let parsed = value;
204
+ if (field === "maxTokens" || field === "port") parsed = Number(value);
205
+ else if (value === "true") parsed = true;
206
+ else if (value === "false") parsed = false;
176
207
  raw[section][field] = parsed;
177
208
  } else {
178
209
  raw[key] = value;
@@ -203,7 +234,18 @@ var init_loader = __esm({
203
234
  "ai.provider": { env: "VIZZOR_AI_PROVIDER", nested: "ai" },
204
235
  "ai.model": { env: "VIZZOR_AI_MODEL", nested: "ai" },
205
236
  "ai.maxTokens": { env: "VIZZOR_AI_MAX_TOKENS", nested: "ai" },
206
- "ai.ollamaHost": { env: "OLLAMA_HOST", nested: "ai" }
237
+ "ai.ollamaHost": { env: "OLLAMA_HOST", nested: "ai" },
238
+ "database.type": { env: "DATABASE_TYPE", nested: "database" },
239
+ "database.url": { env: "DATABASE_URL", nested: "database" },
240
+ "ml.enabled": { env: "ML_ENABLED", nested: "ml" },
241
+ "ml.sidecarUrl": { env: "ML_SIDECAR_URL", nested: "ml" },
242
+ "ml.fallbackToRules": { env: "ML_FALLBACK_TO_RULES", nested: "ml" },
243
+ "api.port": { env: "API_PORT", nested: "api" },
244
+ "api.host": { env: "API_HOST", nested: "api" },
245
+ "api.enableAuth": { env: "API_ENABLE_AUTH", nested: "api" },
246
+ "api.corsOrigin": { env: "API_CORS_ORIGIN", nested: "api" },
247
+ "n8n.enabled": { env: "N8N_ENABLED", nested: "n8n" },
248
+ "n8n.webhookUrl": { env: "N8N_WEBHOOK_URL", nested: "n8n" }
207
249
  };
208
250
  }
209
251
  });
@@ -826,6 +868,329 @@ var init_registry = __esm({
826
868
  }
827
869
  });
828
870
 
871
+ // src/utils/logger.ts
872
+ import pino from "pino";
873
+ function createLogger(name) {
874
+ const isDev = process.env["NODE_ENV"] !== "production";
875
+ if (isDev) {
876
+ return pino({
877
+ name,
878
+ level,
879
+ transport: {
880
+ target: "pino-pretty",
881
+ options: {
882
+ colorize: true
883
+ }
884
+ }
885
+ });
886
+ }
887
+ return pino({ name, level });
888
+ }
889
+ var level;
890
+ var init_logger = __esm({
891
+ "src/utils/logger.ts"() {
892
+ "use strict";
893
+ level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
894
+ }
895
+ });
896
+
897
+ // src/ml/client.ts
898
+ function initMLClient(url) {
899
+ mlClient = new MLClient(url);
900
+ return mlClient;
901
+ }
902
+ function getMLClient() {
903
+ return mlClient;
904
+ }
905
+ var log, MLClient, mlClient;
906
+ var init_client = __esm({
907
+ "src/ml/client.ts"() {
908
+ "use strict";
909
+ init_logger();
910
+ log = createLogger("ml-client");
911
+ MLClient = class {
912
+ baseUrl;
913
+ healthy = false;
914
+ constructor(baseUrl) {
915
+ this.baseUrl = baseUrl.replace(/\/$/, "");
916
+ }
917
+ async predict(features) {
918
+ try {
919
+ const res = await fetch(`${this.baseUrl}/predict`, {
920
+ method: "POST",
921
+ headers: { "Content-Type": "application/json" },
922
+ body: JSON.stringify(features),
923
+ signal: AbortSignal.timeout(5e3)
924
+ });
925
+ if (!res.ok) return null;
926
+ return await res.json();
927
+ } catch (err) {
928
+ log.debug(`ML predict failed: ${err instanceof Error ? err.message : String(err)}`);
929
+ return null;
930
+ }
931
+ }
932
+ async batchPredict(features) {
933
+ try {
934
+ const res = await fetch(`${this.baseUrl}/predict/batch`, {
935
+ method: "POST",
936
+ headers: { "Content-Type": "application/json" },
937
+ body: JSON.stringify({ features }),
938
+ signal: AbortSignal.timeout(15e3)
939
+ });
940
+ if (!res.ok) return [];
941
+ const data = await res.json();
942
+ return data.predictions;
943
+ } catch (err) {
944
+ log.debug(`ML batch predict failed: ${err instanceof Error ? err.message : String(err)}`);
945
+ return [];
946
+ }
947
+ }
948
+ async detectAnomalies(flows) {
949
+ try {
950
+ const res = await fetch(`${this.baseUrl}/anomalies`, {
951
+ method: "POST",
952
+ headers: { "Content-Type": "application/json" },
953
+ body: JSON.stringify({ flows }),
954
+ signal: AbortSignal.timeout(1e4)
955
+ });
956
+ if (!res.ok) return [];
957
+ const data = await res.json();
958
+ return data.anomalies;
959
+ } catch (err) {
960
+ log.debug(`ML anomaly detection failed: ${err instanceof Error ? err.message : String(err)}`);
961
+ return [];
962
+ }
963
+ }
964
+ async healthCheck() {
965
+ try {
966
+ const res = await fetch(`${this.baseUrl}/health`, {
967
+ signal: AbortSignal.timeout(3e3)
968
+ });
969
+ this.healthy = res.ok;
970
+ return this.healthy;
971
+ } catch {
972
+ this.healthy = false;
973
+ return false;
974
+ }
975
+ }
976
+ async predictRug(features) {
977
+ try {
978
+ const res = await fetch(`${this.baseUrl}/predict/rug`, {
979
+ method: "POST",
980
+ headers: { "Content-Type": "application/json" },
981
+ body: JSON.stringify(features),
982
+ signal: AbortSignal.timeout(5e3)
983
+ });
984
+ if (!res.ok) return null;
985
+ return await res.json();
986
+ } catch (err) {
987
+ log.debug(`ML rug predict failed: ${err instanceof Error ? err.message : String(err)}`);
988
+ return null;
989
+ }
990
+ }
991
+ async classifyWallet(features) {
992
+ try {
993
+ const res = await fetch(`${this.baseUrl}/predict/wallet`, {
994
+ method: "POST",
995
+ headers: { "Content-Type": "application/json" },
996
+ body: JSON.stringify(features),
997
+ signal: AbortSignal.timeout(5e3)
998
+ });
999
+ if (!res.ok) return null;
1000
+ return await res.json();
1001
+ } catch (err) {
1002
+ log.debug(`ML wallet classify failed: ${err instanceof Error ? err.message : String(err)}`);
1003
+ return null;
1004
+ }
1005
+ }
1006
+ async analyzeSentiment(text) {
1007
+ try {
1008
+ const res = await fetch(`${this.baseUrl}/predict/sentiment`, {
1009
+ method: "POST",
1010
+ headers: { "Content-Type": "application/json" },
1011
+ body: JSON.stringify({ text }),
1012
+ signal: AbortSignal.timeout(5e3)
1013
+ });
1014
+ if (!res.ok) return null;
1015
+ return await res.json();
1016
+ } catch (err) {
1017
+ log.debug(`ML sentiment failed: ${err instanceof Error ? err.message : String(err)}`);
1018
+ return null;
1019
+ }
1020
+ }
1021
+ async analyzeSentimentBatch(texts) {
1022
+ try {
1023
+ const res = await fetch(`${this.baseUrl}/predict/sentiment/batch`, {
1024
+ method: "POST",
1025
+ headers: { "Content-Type": "application/json" },
1026
+ body: JSON.stringify({ texts }),
1027
+ signal: AbortSignal.timeout(15e3)
1028
+ });
1029
+ if (!res.ok) return [];
1030
+ const data = await res.json();
1031
+ return data.results;
1032
+ } catch (err) {
1033
+ log.debug(`ML sentiment batch failed: ${err instanceof Error ? err.message : String(err)}`);
1034
+ return [];
1035
+ }
1036
+ }
1037
+ // -----------------------------------------------------------------------
1038
+ // v0.11.0 — New ML endpoints
1039
+ // -----------------------------------------------------------------------
1040
+ async scoreTrend(features) {
1041
+ try {
1042
+ const res = await fetch(`${this.baseUrl}/predict/trend`, {
1043
+ method: "POST",
1044
+ headers: { "Content-Type": "application/json" },
1045
+ body: JSON.stringify(features),
1046
+ signal: AbortSignal.timeout(5e3)
1047
+ });
1048
+ if (!res.ok) return null;
1049
+ return await res.json();
1050
+ } catch (err) {
1051
+ log.debug(`ML trend score failed: ${err instanceof Error ? err.message : String(err)}`);
1052
+ return null;
1053
+ }
1054
+ }
1055
+ async interpretTA(features) {
1056
+ try {
1057
+ const res = await fetch(`${this.baseUrl}/predict/ta`, {
1058
+ method: "POST",
1059
+ headers: { "Content-Type": "application/json" },
1060
+ body: JSON.stringify(features),
1061
+ signal: AbortSignal.timeout(5e3)
1062
+ });
1063
+ if (!res.ok) return null;
1064
+ return await res.json();
1065
+ } catch (err) {
1066
+ log.debug(`ML TA interpret failed: ${err instanceof Error ? err.message : String(err)}`);
1067
+ return null;
1068
+ }
1069
+ }
1070
+ async evaluateStrategy(features) {
1071
+ try {
1072
+ const res = await fetch(`${this.baseUrl}/predict/strategy`, {
1073
+ method: "POST",
1074
+ headers: { "Content-Type": "application/json" },
1075
+ body: JSON.stringify(features),
1076
+ signal: AbortSignal.timeout(5e3)
1077
+ });
1078
+ if (!res.ok) return null;
1079
+ return await res.json();
1080
+ } catch (err) {
1081
+ log.debug(`ML strategy eval failed: ${err instanceof Error ? err.message : String(err)}`);
1082
+ return null;
1083
+ }
1084
+ }
1085
+ async detectRegime(features) {
1086
+ try {
1087
+ const res = await fetch(`${this.baseUrl}/predict/regime`, {
1088
+ method: "POST",
1089
+ headers: { "Content-Type": "application/json" },
1090
+ body: JSON.stringify(features),
1091
+ signal: AbortSignal.timeout(5e3)
1092
+ });
1093
+ if (!res.ok) return null;
1094
+ return await res.json();
1095
+ } catch (err) {
1096
+ log.debug(`ML regime detect failed: ${err instanceof Error ? err.message : String(err)}`);
1097
+ return null;
1098
+ }
1099
+ }
1100
+ async scoreProjectRisk(features) {
1101
+ try {
1102
+ const res = await fetch(`${this.baseUrl}/predict/project-risk`, {
1103
+ method: "POST",
1104
+ headers: { "Content-Type": "application/json" },
1105
+ body: JSON.stringify(features),
1106
+ signal: AbortSignal.timeout(5e3)
1107
+ });
1108
+ if (!res.ok) return null;
1109
+ return await res.json();
1110
+ } catch (err) {
1111
+ log.debug(`ML project risk failed: ${err instanceof Error ? err.message : String(err)}`);
1112
+ return null;
1113
+ }
1114
+ }
1115
+ async optimizePortfolio(features) {
1116
+ try {
1117
+ const res = await fetch(`${this.baseUrl}/predict/portfolio-opt`, {
1118
+ method: "POST",
1119
+ headers: { "Content-Type": "application/json" },
1120
+ body: JSON.stringify(features),
1121
+ signal: AbortSignal.timeout(5e3)
1122
+ });
1123
+ if (!res.ok) return null;
1124
+ return await res.json();
1125
+ } catch (err) {
1126
+ log.debug(`ML portfolio opt failed: ${err instanceof Error ? err.message : String(err)}`);
1127
+ return null;
1128
+ }
1129
+ }
1130
+ async classifyIntent(text) {
1131
+ try {
1132
+ const res = await fetch(`${this.baseUrl}/predict/intent`, {
1133
+ method: "POST",
1134
+ headers: { "Content-Type": "application/json" },
1135
+ body: JSON.stringify({ text }),
1136
+ signal: AbortSignal.timeout(5e3)
1137
+ });
1138
+ if (!res.ok) return null;
1139
+ return await res.json();
1140
+ } catch (err) {
1141
+ log.debug(`ML intent classify failed: ${err instanceof Error ? err.message : String(err)}`);
1142
+ return null;
1143
+ }
1144
+ }
1145
+ async scoreBytecodeRisk(features) {
1146
+ try {
1147
+ const res = await fetch(`${this.baseUrl}/predict/bytecode-risk`, {
1148
+ method: "POST",
1149
+ headers: { "Content-Type": "application/json" },
1150
+ body: JSON.stringify(features),
1151
+ signal: AbortSignal.timeout(5e3)
1152
+ });
1153
+ if (!res.ok) return null;
1154
+ return await res.json();
1155
+ } catch (err) {
1156
+ log.debug(`ML bytecode risk failed: ${err instanceof Error ? err.message : String(err)}`);
1157
+ return null;
1158
+ }
1159
+ }
1160
+ async predictPortfolioForward(features) {
1161
+ try {
1162
+ const res = await fetch(`${this.baseUrl}/predict/portfolio-forward`, {
1163
+ method: "POST",
1164
+ headers: { "Content-Type": "application/json" },
1165
+ body: JSON.stringify(features),
1166
+ signal: AbortSignal.timeout(5e3)
1167
+ });
1168
+ if (!res.ok) return null;
1169
+ return await res.json();
1170
+ } catch (err) {
1171
+ log.debug(`ML portfolio forward failed: ${err instanceof Error ? err.message : String(err)}`);
1172
+ return null;
1173
+ }
1174
+ }
1175
+ async getModelHealth() {
1176
+ try {
1177
+ const res = await fetch(`${this.baseUrl}/health`, {
1178
+ signal: AbortSignal.timeout(3e3)
1179
+ });
1180
+ if (!res.ok) return null;
1181
+ return await res.json();
1182
+ } catch {
1183
+ return null;
1184
+ }
1185
+ }
1186
+ isHealthy() {
1187
+ return this.healthy;
1188
+ }
1189
+ };
1190
+ mlClient = null;
1191
+ }
1192
+ });
1193
+
829
1194
  // src/core/scanner/project-analyzer.ts
830
1195
  async function analyzeProject(address, adapter) {
831
1196
  const [token, code, holders] = await Promise.allSettled([
@@ -844,17 +1209,48 @@ async function analyzeProject(address, adapter) {
844
1209
  );
845
1210
  const totalSupply = tokenInfo?.totalSupply ?? 0n;
846
1211
  const topHolderPercentage = topHolders.length > 0 && totalSupply > 0n ? Number((topHolders[0]?.balance ?? 0n) * 10000n / totalSupply) / 100 : 0;
1212
+ const topHoldersMapped = topHolders.map((h) => ({
1213
+ address: h.address,
1214
+ percentage: totalSupply > 0n ? Number(h.balance * 10000n / totalSupply) / 100 : 0
1215
+ }));
1216
+ const top10Pct = topHoldersMapped.reduce((sum, h) => sum + h.percentage, 0);
1217
+ let mlRisk;
1218
+ const mlClient2 = getMLClient();
1219
+ if (mlClient2) {
1220
+ try {
1221
+ const result = await mlClient2.scoreProjectRisk({
1222
+ bytecode_size: contractCode.length,
1223
+ is_verified: hasSourceCode ? 1 : 0,
1224
+ holder_concentration: topHolderPercentage,
1225
+ has_proxy: 0,
1226
+ has_mint: 0,
1227
+ has_pause: 0,
1228
+ has_blacklist: 0,
1229
+ liquidity_locked: 0,
1230
+ buy_tax: 0,
1231
+ sell_tax: 0,
1232
+ contract_age_days: 0,
1233
+ total_transfers: 0,
1234
+ owner_balance_pct: topHolderPercentage,
1235
+ is_open_source: hasSourceCode ? 1 : 0,
1236
+ top10_holder_pct: top10Pct,
1237
+ has_token_info: tokenInfo ? 1 : 0
1238
+ });
1239
+ if (result) {
1240
+ mlRisk = result;
1241
+ }
1242
+ } catch {
1243
+ }
1244
+ }
847
1245
  return {
848
1246
  token: tokenInfo,
849
1247
  contractVerified: hasSourceCode,
850
1248
  hasSourceCode,
851
1249
  holderConcentration: topHolderPercentage,
852
- topHolders: topHolders.map((h) => ({
853
- address: h.address,
854
- percentage: totalSupply > 0n ? Number(h.balance * 10000n / totalSupply) / 100 : 0
855
- })),
1250
+ topHolders: topHoldersMapped,
856
1251
  riskIndicators,
857
- riskScore: Math.min(100, riskScore)
1252
+ riskScore: Math.min(100, riskScore),
1253
+ mlRisk
858
1254
  };
859
1255
  }
860
1256
  function evaluateRiskIndicators(token, hasSource, topHolders) {
@@ -889,16 +1285,30 @@ function evaluateRiskIndicators(token, hasSource, topHolders) {
889
1285
  var init_project_analyzer = __esm({
890
1286
  "src/core/scanner/project-analyzer.ts"() {
891
1287
  "use strict";
1288
+ init_client();
892
1289
  }
893
1290
  });
894
1291
 
895
1292
  // src/core/scanner/risk-scorer.ts
896
1293
  function assessRisk(analysis) {
897
- const { riskScore, riskIndicators } = analysis;
898
- const level2 = getRiskLevel(riskScore);
1294
+ const { riskScore, riskIndicators, mlRisk } = analysis;
1295
+ const effectiveScore = mlRisk ? Math.round(mlRisk.risk_probability * 100) : riskScore;
1296
+ const level2 = getRiskLevel(effectiveScore);
899
1297
  const factors = riskIndicators.filter((i) => i.detected).map((i) => `${i.name}: ${i.description} (+${i.points})`);
1298
+ if (mlRisk) {
1299
+ for (const f of mlRisk.risk_factors) {
1300
+ factors.push(`ML: ${f.factor} (importance: ${(f.importance * 100).toFixed(1)}%)`);
1301
+ }
1302
+ }
900
1303
  const summary = buildSummary(level2, factors.length);
901
- return { score: riskScore, level: level2, summary, factors };
1304
+ return {
1305
+ score: effectiveScore,
1306
+ level: level2,
1307
+ summary,
1308
+ factors,
1309
+ mlScore: mlRisk ? Math.round(mlRisk.risk_probability * 100) : void 0,
1310
+ mlLevel: mlRisk?.risk_level
1311
+ };
902
1312
  }
903
1313
  function getRiskLevel(score) {
904
1314
  if (score <= 20) return "low";
@@ -1134,6 +1544,7 @@ var init_market = __esm({
1134
1544
  "src/core/trends/market.ts"() {
1135
1545
  "use strict";
1136
1546
  init_dexscreener();
1547
+ init_client();
1137
1548
  }
1138
1549
  });
1139
1550
 
@@ -1189,20 +1600,47 @@ async function analyzeSentiment(query) {
1189
1600
  }
1190
1601
  const news = await fetchCryptoNews(query, apiToken);
1191
1602
  if (news.length > 0) {
1192
- let positiveCount = 0;
1193
- let negativeCount = 0;
1194
- for (const article of news) {
1195
- if (article.sentiment === "positive") positiveCount++;
1196
- else if (article.sentiment === "negative") negativeCount++;
1603
+ let mlClient2 = getMLClient();
1604
+ if (!mlClient2) {
1605
+ try {
1606
+ const cfg = getConfig();
1607
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
1608
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
1609
+ }
1610
+ } catch {
1611
+ }
1612
+ }
1613
+ let newsScore;
1614
+ let mlSentiment;
1615
+ let mlConfidence;
1616
+ let mlTopics;
1617
+ if (mlClient2) {
1618
+ const headlines = news.slice(0, 10).map((n) => n.title);
1619
+ const mlResults = await mlClient2.analyzeSentimentBatch(headlines);
1620
+ if (mlResults.length > 0) {
1621
+ const avgScore = mlResults.reduce((s, r) => s + r.score, 0) / mlResults.length;
1622
+ const avgConf = mlResults.reduce((s, r) => s + r.confidence, 0) / mlResults.length;
1623
+ const allTopics = [...new Set(mlResults.flatMap((r) => r.key_topics))];
1624
+ newsScore = avgScore;
1625
+ mlSentiment = avgScore > 0.2 ? "bullish" : avgScore < -0.2 ? "bearish" : "neutral";
1626
+ mlConfidence = avgConf;
1627
+ mlTopics = allTopics.slice(0, 5);
1628
+ } else {
1629
+ newsScore = countBasedScore(news);
1630
+ }
1631
+ } else {
1632
+ newsScore = countBasedScore(news);
1197
1633
  }
1198
1634
  const total = news.length;
1199
- const newsScore = total > 0 ? (positiveCount - negativeCount) / total : 0;
1200
1635
  sources.push({
1201
- source: "CryptoPanic News",
1636
+ source: mlSentiment ? "ML NLP Sentiment" : "CryptoPanic News",
1202
1637
  score: newsScore,
1203
1638
  volume: total,
1204
1639
  trending: total > 5,
1205
- topMentions: news.slice(0, 3).map((n) => n.title)
1640
+ topMentions: news.slice(0, 3).map((n) => n.title),
1641
+ mlSentiment,
1642
+ mlConfidence,
1643
+ mlTopics
1206
1644
  });
1207
1645
  }
1208
1646
  } catch {
@@ -1246,12 +1684,23 @@ async function analyzeSentiment(query) {
1246
1684
  }
1247
1685
  return { overall, sources, consensus };
1248
1686
  }
1687
+ function countBasedScore(news) {
1688
+ let pos = 0;
1689
+ let neg = 0;
1690
+ for (const article of news) {
1691
+ if (article.sentiment === "positive") pos++;
1692
+ else if (article.sentiment === "negative") neg++;
1693
+ }
1694
+ const total = news.length;
1695
+ return total > 0 ? (pos - neg) / total : 0;
1696
+ }
1249
1697
  var init_sentiment = __esm({
1250
1698
  "src/core/trends/sentiment.ts"() {
1251
1699
  "use strict";
1252
1700
  init_cryptopanic();
1253
1701
  init_dexscreener();
1254
1702
  init_loader();
1703
+ init_client();
1255
1704
  }
1256
1705
  });
1257
1706
 
@@ -1536,15 +1985,74 @@ async function analyzeTechnicals(symbol, timeframe = "4h") {
1536
1985
  }
1537
1986
  const atr = calculateATR(highs, lows, closes);
1538
1987
  if (atr !== null) {
1539
- const currentPrice = closes[closes.length - 1];
1540
- signals.push(interpretATR(atr, currentPrice));
1988
+ const currentPrice2 = closes[closes.length - 1];
1989
+ signals.push(interpretATR(atr, currentPrice2));
1541
1990
  }
1542
1991
  const obv = calculateOBV(closes, volumes);
1543
1992
  if (obv !== null) {
1544
- const priceChange = closes.length >= 2 ? closes[closes.length - 1] - closes[closes.length - 2] : 0;
1545
- signals.push(interpretOBV(obv, priceChange));
1993
+ const priceChange2 = closes.length >= 2 ? closes[closes.length - 1] - closes[closes.length - 2] : 0;
1994
+ signals.push(interpretOBV(obv, priceChange2));
1546
1995
  }
1547
1996
  const sma20 = calculateSMA(closes, 20);
1997
+ const currentPrice = closes[closes.length - 1];
1998
+ const prevPrice = closes.length >= 2 ? closes[closes.length - 2] : currentPrice;
1999
+ const priceChange = currentPrice - prevPrice;
2000
+ const ema12Val = ema12.length > 0 ? ema12[ema12.length - 1] : 0;
2001
+ const ema26Val = ema26.length > 0 ? ema26[ema26.length - 1] : 0;
2002
+ const emaCrossPct = ema26Val !== 0 ? (ema12Val - ema26Val) / ema26Val * 100 : 0;
2003
+ const atrPct = atr !== null && currentPrice > 0 ? atr / currentPrice * 100 : 0;
2004
+ const bbBandwidth = bb !== null && bb.middle > 0 ? (bb.upper - bb.lower) / bb.middle * 100 : 0;
2005
+ const mlClient2 = getMLClient();
2006
+ if (mlClient2) {
2007
+ try {
2008
+ const mlResult = await mlClient2.interpretTA({
2009
+ rsi: rsi ?? 50,
2010
+ macd_histogram: macd?.histogram ?? 0,
2011
+ macd_line: macd?.macd ?? 0,
2012
+ macd_signal: macd?.signal ?? 0,
2013
+ bb_percent_b: bb?.percentB ?? 0.5,
2014
+ bb_bandwidth: bbBandwidth,
2015
+ ema12: ema12Val,
2016
+ ema26: ema26Val,
2017
+ ema_cross_pct: emaCrossPct,
2018
+ atr: atr ?? 0,
2019
+ atr_pct: atrPct,
2020
+ obv: obv ?? 0,
2021
+ price_change: priceChange
2022
+ });
2023
+ if (mlResult) {
2024
+ const mlSignals = mlResult.signals.map((s) => ({
2025
+ name: s.name,
2026
+ value: 0,
2027
+ signal: s.direction,
2028
+ strength: s.strength,
2029
+ description: s.description
2030
+ }));
2031
+ return {
2032
+ symbol: symbol.toUpperCase(),
2033
+ timeframe,
2034
+ signals: mlSignals,
2035
+ composite: {
2036
+ direction: mlResult.composite.direction,
2037
+ score: mlResult.composite.score,
2038
+ confidence: mlResult.composite.confidence
2039
+ },
2040
+ indicators: {
2041
+ rsi,
2042
+ macd,
2043
+ bollingerBands: bb,
2044
+ ema12: ema12Val || null,
2045
+ ema26: ema26Val || null,
2046
+ sma20: sma20.length > 0 ? sma20[sma20.length - 1] : null,
2047
+ atr,
2048
+ obv
2049
+ },
2050
+ timestamp: Date.now()
2051
+ };
2052
+ }
2053
+ } catch {
2054
+ }
2055
+ }
1548
2056
  const composite = calculateComposite(signals);
1549
2057
  return {
1550
2058
  symbol: symbol.toUpperCase(),
@@ -1555,8 +2063,8 @@ async function analyzeTechnicals(symbol, timeframe = "4h") {
1555
2063
  rsi,
1556
2064
  macd,
1557
2065
  bollingerBands: bb,
1558
- ema12: ema12.length > 0 ? ema12[ema12.length - 1] : null,
1559
- ema26: ema26.length > 0 ? ema26[ema26.length - 1] : null,
2066
+ ema12: ema12Val || null,
2067
+ ema26: ema26Val || null,
1560
2068
  sma20: sma20.length > 0 ? sma20[sma20.length - 1] : null,
1561
2069
  atr,
1562
2070
  obv
@@ -1737,6 +2245,7 @@ var init_analyzer = __esm({
1737
2245
  "use strict";
1738
2246
  init_binance();
1739
2247
  init_indicators();
2248
+ init_client();
1740
2249
  WEIGHTS = {
1741
2250
  RSI: 20,
1742
2251
  MACD: 20,
@@ -1796,46 +2305,6 @@ var init_fear_greed = __esm({
1796
2305
  }
1797
2306
  });
1798
2307
 
1799
- // src/utils/logger.ts
1800
- import pino from "pino";
1801
- function createLogger(name) {
1802
- const isDev = process.env["NODE_ENV"] !== "production";
1803
- if (isDev) {
1804
- return pino({
1805
- name,
1806
- level,
1807
- transport: {
1808
- target: "pino-pretty",
1809
- options: {
1810
- colorize: true
1811
- }
1812
- }
1813
- });
1814
- }
1815
- return pino({ name, level });
1816
- }
1817
- var level;
1818
- var init_logger = __esm({
1819
- "src/utils/logger.ts"() {
1820
- "use strict";
1821
- level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
1822
- }
1823
- });
1824
-
1825
- // src/ml/client.ts
1826
- function getMLClient() {
1827
- return mlClient;
1828
- }
1829
- var log, mlClient;
1830
- var init_client = __esm({
1831
- "src/ml/client.ts"() {
1832
- "use strict";
1833
- init_logger();
1834
- log = createLogger("ml-client");
1835
- mlClient = null;
1836
- }
1837
- });
1838
-
1839
2308
  // src/ml/feature-engineer.ts
1840
2309
  async function buildFeatureVector(symbol) {
1841
2310
  const [ta, fundingResult, tickerResult, fgResult, klines] = await Promise.allSettled([
@@ -2031,7 +2500,16 @@ async function generatePrediction(symbol) {
2031
2500
  composite: Math.round(composite),
2032
2501
  disclaimer: "This is not financial advice. Predictions are based on historical data and AI analysis. Always do your own research."
2033
2502
  };
2034
- const mlClient2 = getMLClient();
2503
+ let mlClient2 = getMLClient();
2504
+ if (!mlClient2) {
2505
+ try {
2506
+ const cfg = getConfig();
2507
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
2508
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
2509
+ }
2510
+ } catch {
2511
+ }
2512
+ }
2035
2513
  if (mlClient2) {
2036
2514
  try {
2037
2515
  const features = await buildFeatureVector(symbol);
@@ -2070,6 +2548,7 @@ var init_predictor = __esm({
2070
2548
  init_binance();
2071
2549
  init_client();
2072
2550
  init_feature_engineer();
2551
+ init_loader();
2073
2552
  WEIGHTS2 = {
2074
2553
  technical: 40,
2075
2554
  sentiment: 20,
@@ -2158,7 +2637,39 @@ async function analyzeWallet(address, adapter) {
2158
2637
  ]);
2159
2638
  const walletBalance = balance.status === "fulfilled" ? balance.value : 0n;
2160
2639
  const txHistory = transactions.status === "fulfilled" ? transactions.value : [];
2161
- const patterns = detectPatterns(txHistory.length);
2640
+ const patterns = detectPatterns(txHistory);
2641
+ let mlBehavior;
2642
+ try {
2643
+ let mlClient2 = getMLClient();
2644
+ if (!mlClient2) {
2645
+ try {
2646
+ const cfg = getConfig();
2647
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
2648
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
2649
+ }
2650
+ } catch {
2651
+ }
2652
+ }
2653
+ if (mlClient2 && txHistory.length > 0) {
2654
+ const features = buildWalletFeatures(txHistory);
2655
+ const result = await mlClient2.classifyWallet(features);
2656
+ if (result) {
2657
+ mlBehavior = result;
2658
+ }
2659
+ }
2660
+ } catch {
2661
+ }
2662
+ let riskLevel = "clean";
2663
+ if (patterns.some((p) => p.severity === "danger")) {
2664
+ riskLevel = "flagged";
2665
+ } else if (patterns.some((p) => p.severity === "warning")) {
2666
+ riskLevel = "suspicious";
2667
+ }
2668
+ if (mlBehavior && mlBehavior.risk_score > 0.6) {
2669
+ riskLevel = "flagged";
2670
+ } else if (mlBehavior && mlBehavior.risk_score > 0.3 && riskLevel === "clean") {
2671
+ riskLevel = "suspicious";
2672
+ }
2162
2673
  return {
2163
2674
  address,
2164
2675
  chain: adapter.chainId,
@@ -2166,23 +2677,129 @@ async function analyzeWallet(address, adapter) {
2166
2677
  transactionCount: txHistory.length,
2167
2678
  tokenBalances: [],
2168
2679
  patterns,
2169
- riskLevel: patterns.some((p) => p.severity === "danger") ? "flagged" : "clean"
2680
+ riskLevel,
2681
+ mlBehavior
2682
+ };
2683
+ }
2684
+ function buildWalletFeatures(txHistory) {
2685
+ const txCount = txHistory.length;
2686
+ if (txCount === 0) {
2687
+ return {
2688
+ tx_count: 0,
2689
+ avg_value_eth: 0,
2690
+ max_value_eth: 0,
2691
+ avg_gas_used: 0,
2692
+ unique_recipients: 0,
2693
+ unique_methods: 0,
2694
+ time_span_hours: 0,
2695
+ avg_interval_seconds: 3600,
2696
+ min_interval_seconds: 60,
2697
+ contract_interaction_pct: 0,
2698
+ self_transfer_pct: 0,
2699
+ high_value_tx_pct: 0,
2700
+ failed_tx_pct: 0,
2701
+ token_diversity: 0
2702
+ };
2703
+ }
2704
+ const values = txHistory.map((tx) => Number(tx.value) / 1e18);
2705
+ const gasValues = txHistory.map((tx) => Number(tx.gasUsed ?? 21e3));
2706
+ const recipients = new Set(txHistory.map((tx) => tx.to));
2707
+ const methods = new Set(
2708
+ txHistory.filter((tx) => tx.input && tx.input.length > 10).map((tx) => tx.input.slice(0, 10))
2709
+ );
2710
+ const timestamps = txHistory.map((tx) => tx.timestamp).sort((a, b) => a - b);
2711
+ const timeSpan = timestamps.length > 1 ? (timestamps.at(-1) - timestamps[0]) / 3600 : 0;
2712
+ const intervals = [];
2713
+ for (let i = 1; i < timestamps.length; i++) {
2714
+ intervals.push(timestamps[i] - timestamps[i - 1]);
2715
+ }
2716
+ const selfTxs = txHistory.filter((tx) => tx.from === tx.to).length;
2717
+ const contractTxs = txHistory.filter((tx) => tx.input && tx.input.length > 10).length;
2718
+ const revertedTxs = txHistory.filter((tx) => tx.status === "reverted").length;
2719
+ const highValueTxs = values.filter((v) => v > 10).length;
2720
+ return {
2721
+ tx_count: txCount,
2722
+ avg_value_eth: values.reduce((a, b) => a + b, 0) / txCount,
2723
+ max_value_eth: Math.max(...values),
2724
+ avg_gas_used: gasValues.reduce((a, b) => a + b, 0) / txCount,
2725
+ unique_recipients: recipients.size,
2726
+ unique_methods: methods.size,
2727
+ time_span_hours: timeSpan,
2728
+ avg_interval_seconds: intervals.length > 0 ? intervals.reduce((a, b) => a + b, 0) / intervals.length : 3600,
2729
+ min_interval_seconds: intervals.length > 0 ? Math.min(...intervals) : 60,
2730
+ contract_interaction_pct: contractTxs / txCount,
2731
+ self_transfer_pct: selfTxs / txCount,
2732
+ high_value_tx_pct: highValueTxs / txCount,
2733
+ failed_tx_pct: revertedTxs / txCount,
2734
+ token_diversity: methods.size
2170
2735
  };
2171
2736
  }
2172
- function detectPatterns(txCount) {
2737
+ function detectPatterns(txHistory) {
2173
2738
  const patterns = [];
2739
+ const txCount = txHistory.length;
2174
2740
  if (txCount === 0) {
2175
2741
  patterns.push({
2176
2742
  type: "new_wallet",
2177
2743
  description: "Wallet has no transaction history",
2178
2744
  severity: "info"
2179
2745
  });
2746
+ return patterns;
2747
+ }
2748
+ if (txCount > 50) {
2749
+ const timestamps = txHistory.map((tx) => tx.timestamp).sort((a, b) => a - b);
2750
+ const intervals = [];
2751
+ for (let i = 1; i < timestamps.length; i++) {
2752
+ intervals.push(timestamps[i] - timestamps[i - 1]);
2753
+ }
2754
+ const minInterval = Math.min(...intervals);
2755
+ if (minInterval < 5) {
2756
+ patterns.push({
2757
+ type: "rapid_transactions",
2758
+ description: `Extremely rapid transactions detected (${minInterval}s apart) \u2014 possible bot activity`,
2759
+ severity: "warning"
2760
+ });
2761
+ }
2762
+ }
2763
+ const revertedTxs = txHistory.filter((tx) => tx.status === "reverted").length;
2764
+ if (txCount > 10 && revertedTxs / txCount > 0.3) {
2765
+ patterns.push({
2766
+ type: "high_failure_rate",
2767
+ description: `${Math.round(revertedTxs / txCount * 100)}% of transactions failed \u2014 possible sniper or MEV bot`,
2768
+ severity: "warning"
2769
+ });
2770
+ }
2771
+ const values = txHistory.map((tx) => Number(tx.value) / 1e18);
2772
+ const maxValue = Math.max(...values);
2773
+ if (maxValue > 100) {
2774
+ patterns.push({
2775
+ type: "whale_activity",
2776
+ description: `Large transfers detected (max: ${maxValue.toFixed(2)} ETH)`,
2777
+ severity: "info"
2778
+ });
2779
+ }
2780
+ const selfTxs = txHistory.filter((tx) => tx.from === tx.to).length;
2781
+ if (selfTxs / txCount > 0.2) {
2782
+ patterns.push({
2783
+ type: "self_transfers",
2784
+ description: `${Math.round(selfTxs / txCount * 100)}% self-transfers \u2014 possible mixing activity`,
2785
+ severity: "danger"
2786
+ });
2787
+ }
2788
+ const contractTxs = txHistory.filter((tx) => tx.input && tx.input.length > 10).length;
2789
+ if (contractTxs / txCount > 0.9 && txCount > 20) {
2790
+ patterns.push({
2791
+ type: "contract_heavy",
2792
+ description: "Almost exclusively interacts with smart contracts \u2014 automated behavior",
2793
+ severity: "warning"
2794
+ });
2180
2795
  }
2181
2796
  return patterns;
2182
2797
  }
2183
2798
  var init_wallet_analyzer = __esm({
2184
2799
  "src/core/forensics/wallet-analyzer.ts"() {
2185
2800
  "use strict";
2801
+ init_client();
2802
+ init_loader();
2186
2803
  }
2187
2804
  });
2188
2805
 
@@ -2767,8 +3384,6 @@ async function handleConfigInit() {
2767
3384
  console.log(` 3. Try scanning a project: ${chalk6.cyan("vizzor scan ethereum")}`);
2768
3385
  }
2769
3386
  async function handleConfigSet(key, value) {
2770
- const configDir = getConfigDir();
2771
- const configPath = resolve(configDir, "config.yaml");
2772
3387
  const isSensitive = key.toLowerCase().includes("key") || key.toLowerCase().includes("token");
2773
3388
  if (isSensitive) {
2774
3389
  const error = validateKey(key, value);
@@ -2780,20 +3395,12 @@ async function handleConfigSet(key, value) {
2780
3395
  console.log(chalk6.yellow(error));
2781
3396
  }
2782
3397
  }
2783
- await loadConfig();
2784
- const config2 = getConfig();
2785
- const updatedConfig = JSON.parse(JSON.stringify(config2));
2786
- if (key.includes(".")) {
2787
- const [section, field] = key.split(".");
2788
- if (!updatedConfig[section] || typeof updatedConfig[section] !== "object") {
2789
- updatedConfig[section] = {};
2790
- }
2791
- const parsed = field === "maxTokens" ? Number(value) : value;
2792
- updatedConfig[section][field] = parsed;
2793
- } else {
2794
- updatedConfig[key] = value;
3398
+ try {
3399
+ saveConfigValue(key, value);
3400
+ } catch (err) {
3401
+ console.log(chalk6.red(err instanceof Error ? err.message : String(err)));
3402
+ return;
2795
3403
  }
2796
- writeFileSync2(configPath, yamlStringify(updatedConfig), "utf-8");
2797
3404
  const displayValue = isSensitive ? maskKey(value) : value;
2798
3405
  console.log(chalk6.green(`Set ${key} = ${displayValue}`));
2799
3406
  }
@@ -3776,10 +4383,25 @@ var init_goplus = __esm({
3776
4383
  async function buildContextBlock(userMessage) {
3777
4384
  const lower = userMessage.toLowerCase();
3778
4385
  const sections = [];
3779
- const addressMatch = userMessage.match(/0x[a-fA-F0-9]{40}/);
4386
+ let mlIntent = null;
4387
+ const mlClientForIntent = getMLClient();
4388
+ if (mlClientForIntent) {
4389
+ try {
4390
+ const intentResult = await mlClientForIntent.classifyIntent(userMessage);
4391
+ if (intentResult && intentResult.confidence > 0.7) {
4392
+ mlIntent = intentResult.intent;
4393
+ }
4394
+ } catch {
4395
+ }
4396
+ }
4397
+ const evmMatch = userMessage.match(/0x[a-fA-F0-9]{40}/);
4398
+ const solanaMatch = userMessage.match(/\b([1-9A-HJ-NP-Za-km-z]{32,44})\b/);
4399
+ const isSolanaAddr = solanaMatch && !evmMatch && /\d/.test(solanaMatch[1]) && solanaMatch[1].length >= 32;
4400
+ const addressMatch = evmMatch ?? (isSolanaAddr ? solanaMatch : null);
4401
+ const detectedChain = evmMatch ? null : isSolanaAddr ? "solana" : null;
3780
4402
  const mentionedTokens = detectTokens(lower);
3781
4403
  const unknownTokens = detectUnknownTokens(userMessage, mentionedTokens);
3782
- const isAnalysisQuery = matchesAny(lower, ANALYSIS_KEYWORDS);
4404
+ const isAnalysisQuery = matchesAny(lower, ANALYSIS_KEYWORDS) || mlIntent === "analysis";
3783
4405
  const tasks = [];
3784
4406
  if (mentionedTokens.length > 0 || addressMatch || matchesAny(lower, PRICE_KEYWORDS)) {
3785
4407
  tasks.push(
@@ -3805,8 +4427,7 @@ async function buildContextBlock(userMessage) {
3805
4427
  }
3806
4428
  }
3807
4429
  if (addressMatch && isAnalysisQuery) {
3808
- const cfg = getConfig();
3809
- const chain = cfg.defaultChain || "ethereum";
4430
+ const chain = detectedChain || getConfig().defaultChain || "ethereum";
3810
4431
  tasks.push(
3811
4432
  fetchSecurityData(addressMatch[0], chain).then((data) => {
3812
4433
  if (data) sections.push(data);
@@ -3847,8 +4468,9 @@ async function buildContextBlock(userMessage) {
3847
4468
  })
3848
4469
  );
3849
4470
  }
3850
- const isBroadQuery = matchesAny(lower, BROAD_KEYWORDS);
3851
- if (isBroadQuery) {
4471
+ const isBroadQuery = matchesAny(lower, BROAD_KEYWORDS) || mlIntent === "broad_overview";
4472
+ const hasSpecificIntent = mentionedTokens.length > 0 || unknownTokens.length > 0 || !!addressMatch;
4473
+ if (isBroadQuery && !hasSpecificIntent) {
3852
4474
  if (!matchesAny(lower, TRENDING_KEYWORDS)) {
3853
4475
  tasks.push(
3854
4476
  fetchTrendingData().then((data) => {
@@ -3870,9 +4492,18 @@ async function buildContextBlock(userMessage) {
3870
4492
  })
3871
4493
  );
3872
4494
  }
3873
- if (!matchesAny(lower, PRICE_KEYWORDS) && mentionedTokens.length === 0 && !addressMatch) {
4495
+ if (!matchesAny(lower, PRICE_KEYWORDS)) {
4496
+ tasks.push(
4497
+ fetchTokenData(["bitcoin", "ethereum", "solana"]).then((data) => {
4498
+ if (data) sections.push(data);
4499
+ })
4500
+ );
4501
+ }
4502
+ } else if (isBroadQuery && hasSpecificIntent) {
4503
+ if (!matchesAny(lower, NEWS_KEYWORDS)) {
4504
+ const symbol = mentionedTokens[0]?.toUpperCase() || unknownTokens[0]?.toUpperCase();
3874
4505
  tasks.push(
3875
- fetchTokenData(["bitcoin", "ethereum", "solana"]).then((data) => {
4506
+ fetchNewsData(symbol).then((data) => {
3876
4507
  if (data) sections.push(data);
3877
4508
  })
3878
4509
  );
@@ -4154,8 +4785,30 @@ async function fetchNewsData(symbol) {
4154
4785
  try {
4155
4786
  const news = await fetchCryptoNews(symbol, getConfig().cryptopanicApiKey);
4156
4787
  if (news.length > 0) {
4788
+ const headlines = news.slice(0, 8);
4157
4789
  const lines = [`## Latest Crypto News${symbol ? ` (${symbol})` : ""}`];
4158
- for (const n of news.slice(0, 8)) {
4790
+ const mlClient2 = getMLClient();
4791
+ if (mlClient2) {
4792
+ try {
4793
+ const texts = headlines.map((n) => n.title);
4794
+ const mlResults = await mlClient2.analyzeSentimentBatch(texts);
4795
+ if (mlResults.length > 0) {
4796
+ for (let i = 0; i < headlines.length; i++) {
4797
+ const n = headlines[i];
4798
+ const ml = mlResults[i];
4799
+ const label = ml ? `${ml.sentiment.toUpperCase()} (${(ml.confidence * 100).toFixed(0)}%)` : n.sentiment.toUpperCase();
4800
+ lines.push(`- [${label}] ${n.title} (${n.source.title}, ${n.publishedAt})`);
4801
+ }
4802
+ const avgScore = mlResults.reduce((s, r) => s + r.score, 0) / mlResults.length;
4803
+ const avgSentiment = avgScore > 0.2 ? "BULLISH" : avgScore < -0.2 ? "BEARISH" : "NEUTRAL";
4804
+ lines.push(`
4805
+ ML Aggregate Sentiment: ${avgSentiment} (score: ${avgScore.toFixed(3)})`);
4806
+ return lines.join("\n");
4807
+ }
4808
+ } catch {
4809
+ }
4810
+ }
4811
+ for (const n of headlines) {
4159
4812
  lines.push(
4160
4813
  `- [${n.sentiment.toUpperCase()}] ${n.title} (${n.source.title}, ${n.publishedAt})`
4161
4814
  );
@@ -4194,7 +4847,7 @@ async function fetchRaisesData() {
4194
4847
  if (raises.length === 0) return null;
4195
4848
  const lines = ["## Recent Crypto Fundraising Rounds (last 30 days)"];
4196
4849
  for (const r of raises.slice(0, 10)) {
4197
- const amount = r.amount ? `$${(r.amount / 1e6).toFixed(1)}M` : "undisclosed";
4850
+ const amount = r.amount ? r.amount >= 1e9 ? `$${(r.amount / 1e9).toFixed(1)}B` : r.amount >= 1e6 ? `$${(r.amount / 1e6).toFixed(1)}M` : r.amount >= 1e3 ? `$${(r.amount / 1e3).toFixed(0)}K` : `$${r.amount.toLocaleString()}` : "undisclosed";
4198
4851
  const date = new Date(r.date * 1e3).toISOString().split("T")[0];
4199
4852
  lines.push(
4200
4853
  `- ${r.name} \u2014 ${r.round} (${amount}) on ${r.chains.join(", ") || "multi-chain"} [${date}]${r.leadInvestors.length > 0 ? ` Led by: ${r.leadInvestors.join(", ")}` : ""}`
@@ -5131,6 +5784,7 @@ var init_context_injector = __esm({
5131
5784
  init_goplus();
5132
5785
  init_loader();
5133
5786
  init_constants();
5787
+ init_client();
5134
5788
  PRICE_KEYWORDS = ["price", "worth", "cost", "value", "how much"];
5135
5789
  TRENDING_KEYWORDS = ["trending", "hot", "popular", "top", "best", "hype"];
5136
5790
  NEWS_KEYWORDS = ["news", "latest", "update", "happening", "announcement"];
@@ -5510,6 +6164,7 @@ var DANGEROUS_FUNCTIONS, DANGEROUS_OPCODES;
5510
6164
  var init_bytecode_scanner = __esm({
5511
6165
  "src/core/forensics/bytecode-scanner.ts"() {
5512
6166
  "use strict";
6167
+ init_client();
5513
6168
  DANGEROUS_FUNCTIONS = [
5514
6169
  // Mint functions — inflation risk
5515
6170
  {
@@ -5667,6 +6322,42 @@ async function detectRugIndicators(tokenAddress, adapter) {
5667
6322
  }
5668
6323
  }
5669
6324
  const riskScore = calculateRugRisk(details);
6325
+ let mlAnalysis;
6326
+ try {
6327
+ let mlClient2 = getMLClient();
6328
+ if (!mlClient2) {
6329
+ try {
6330
+ const cfg = getConfig();
6331
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
6332
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
6333
+ }
6334
+ } catch {
6335
+ }
6336
+ }
6337
+ if (mlClient2) {
6338
+ const mlResult = await mlClient2.predictRug({
6339
+ bytecode_size: hasCode ? code.length : 0,
6340
+ is_verified: hasCode ? 1 : 0,
6341
+ holder_concentration: 0,
6342
+ has_proxy: 0,
6343
+ has_mint: canMint ? 1 : 0,
6344
+ has_pause: canPause ? 1 : 0,
6345
+ has_blacklist: hasBlacklist_ ? 1 : 0,
6346
+ liquidity_locked: 0,
6347
+ buy_tax: 0,
6348
+ sell_tax: 0,
6349
+ contract_age_days: 0,
6350
+ total_transfers: 0,
6351
+ owner_balance_pct: 0,
6352
+ is_open_source: hasCode ? 1 : 0,
6353
+ top10_holder_pct: 0
6354
+ });
6355
+ if (mlResult) {
6356
+ mlAnalysis = mlResult;
6357
+ }
6358
+ }
6359
+ } catch {
6360
+ }
5670
6361
  return {
5671
6362
  isHoneypot: canPause || hasBlacklist_,
5672
6363
  hasLiquidityLock: false,
@@ -5674,8 +6365,9 @@ async function detectRugIndicators(tokenAddress, adapter) {
5674
6365
  ownerCanPause: canPause,
5675
6366
  hasBlacklist: hasBlacklist_,
5676
6367
  highSellTax: false,
5677
- riskScore,
5678
- details
6368
+ riskScore: mlAnalysis ? Math.round(riskScore * 0.4 + mlAnalysis.rug_probability * 100 * 0.6) : riskScore,
6369
+ details,
6370
+ mlAnalysis
5679
6371
  };
5680
6372
  }
5681
6373
  function calculateRugRisk(details) {
@@ -5701,6 +6393,83 @@ var init_rug_detector = __esm({
5701
6393
  "src/core/forensics/rug-detector.ts"() {
5702
6394
  "use strict";
5703
6395
  init_bytecode_scanner();
6396
+ init_client();
6397
+ init_loader();
6398
+ }
6399
+ });
6400
+
6401
+ // src/core/trends/regime.ts
6402
+ async function detectMarketRegime(_symbol, features) {
6403
+ const mlClient2 = getMLClient();
6404
+ if (mlClient2) {
6405
+ try {
6406
+ const result = await mlClient2.detectRegime(features);
6407
+ if (result) {
6408
+ return {
6409
+ regime: result.regime,
6410
+ confidence: result.confidence,
6411
+ probabilities: result.probabilities,
6412
+ model: result.model
6413
+ };
6414
+ }
6415
+ } catch {
6416
+ }
6417
+ }
6418
+ return detectRegimeHeuristic(features);
6419
+ }
6420
+ function detectRegimeHeuristic(features) {
6421
+ const vol = features.volatility_14d;
6422
+ const ret7d = features.returns_7d;
6423
+ const fg = features.fear_greed;
6424
+ const rsi = features.rsi;
6425
+ let regime;
6426
+ let confidence;
6427
+ if (fg < 15 && ret7d < -20) {
6428
+ regime = "capitulation";
6429
+ confidence = 80;
6430
+ } else if (vol > 8) {
6431
+ regime = "volatile";
6432
+ confidence = 70;
6433
+ } else if (vol > 5 && ret7d > 10) {
6434
+ regime = "trending_bull";
6435
+ confidence = 65;
6436
+ } else if (vol > 5 && ret7d < -10) {
6437
+ regime = "trending_bear";
6438
+ confidence = 65;
6439
+ } else if (ret7d > 5 && rsi > 55) {
6440
+ regime = "trending_bull";
6441
+ confidence = 55;
6442
+ } else if (ret7d < -5 && rsi < 45) {
6443
+ regime = "trending_bear";
6444
+ confidence = 55;
6445
+ } else {
6446
+ regime = "ranging";
6447
+ confidence = 60;
6448
+ }
6449
+ const probabilities = {
6450
+ trending_bull: 0.05,
6451
+ trending_bear: 0.05,
6452
+ ranging: 0.05,
6453
+ volatile: 0.05,
6454
+ capitulation: 0.05
6455
+ };
6456
+ probabilities[regime] = confidence / 100;
6457
+ const remaining = 1 - probabilities[regime];
6458
+ const others = Object.keys(probabilities).filter((k) => k !== regime);
6459
+ for (const k of others) {
6460
+ probabilities[k] = remaining / others.length;
6461
+ }
6462
+ return {
6463
+ regime,
6464
+ confidence,
6465
+ probabilities,
6466
+ model: "heuristic-regime-detector"
6467
+ };
6468
+ }
6469
+ var init_regime = __esm({
6470
+ "src/core/trends/regime.ts"() {
6471
+ "use strict";
6472
+ init_client();
5704
6473
  }
5705
6474
  });
5706
6475
 
@@ -5891,6 +6660,7 @@ var momentumStrategy;
5891
6660
  var init_momentum = __esm({
5892
6661
  "src/core/agent/strategies/momentum.ts"() {
5893
6662
  "use strict";
6663
+ init_client();
5894
6664
  momentumStrategy = {
5895
6665
  name: "momentum",
5896
6666
  description: "RSI reversal + MACD confirmation. Buy oversold bounces, sell overbought peaks.",
@@ -5966,6 +6736,7 @@ var trendFollowingStrategy;
5966
6736
  var init_trend_following = __esm({
5967
6737
  "src/core/agent/strategies/trend-following.ts"() {
5968
6738
  "use strict";
6739
+ init_client();
5969
6740
  trendFollowingStrategy = {
5970
6741
  name: "trend-following",
5971
6742
  description: "EMA crossover trend detection. Buy golden cross, sell death cross.",
@@ -6124,7 +6895,7 @@ var init_ml_adaptive = __esm({
6124
6895
  "src/core/agent/strategies/ml-adaptive.ts"() {
6125
6896
  "use strict";
6126
6897
  init_client();
6127
- init_feature_engineer();
6898
+ init_regime();
6128
6899
  mlAdaptiveStrategy = {
6129
6900
  name: "ml-adaptive",
6130
6901
  description: "ML-powered strategy using contextual bandit approach. Uses LSTM/RF predictions when available, falls back to rule-based when < 100 training samples.",
@@ -7074,7 +7845,16 @@ async function handleTool(name, input) {
7074
7845
  }
7075
7846
  case "get_ml_prediction": {
7076
7847
  const symbol = String(params["symbol"] ?? "BTC");
7077
- const mlClient2 = getMLClient();
7848
+ let mlClient2 = getMLClient();
7849
+ if (!mlClient2) {
7850
+ try {
7851
+ const cfg = getConfig();
7852
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
7853
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
7854
+ }
7855
+ } catch {
7856
+ }
7857
+ }
7078
7858
  if (!mlClient2) {
7079
7859
  const prediction = await generatePrediction(symbol);
7080
7860
  return {
@@ -7135,6 +7915,256 @@ async function handleTool(name, input) {
7135
7915
  }
7136
7916
  };
7137
7917
  }
7918
+ case "get_rug_ml_analysis": {
7919
+ const address = String(params["address"] ?? "");
7920
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
7921
+ const adapter = getAdapter(chain);
7922
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
7923
+ const indicators = await detectRugIndicators(address, adapter);
7924
+ let goplus = null;
7925
+ try {
7926
+ goplus = await checkTokenSecurity(address, chain);
7927
+ } catch {
7928
+ }
7929
+ let mlClient2 = getMLClient();
7930
+ if (!mlClient2) {
7931
+ try {
7932
+ const cfg = getConfig();
7933
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
7934
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
7935
+ }
7936
+ } catch {
7937
+ }
7938
+ }
7939
+ let mlResult = indicators.mlAnalysis ?? null;
7940
+ if (!mlResult && mlClient2 && goplus) {
7941
+ mlResult = await mlClient2.predictRug({
7942
+ bytecode_size: 0,
7943
+ is_verified: goplus.isOpenSource ? 1 : 0,
7944
+ holder_concentration: (goplus.creatorPercent ?? 0) + (goplus.ownerPercent ?? 0),
7945
+ has_proxy: goplus.isProxy ? 1 : 0,
7946
+ has_mint: goplus.isMintable ? 1 : 0,
7947
+ has_pause: indicators.ownerCanPause ? 1 : 0,
7948
+ has_blacklist: indicators.hasBlacklist ? 1 : 0,
7949
+ liquidity_locked: 0,
7950
+ buy_tax: goplus.buyTax ?? 0,
7951
+ sell_tax: goplus.sellTax ?? 0,
7952
+ contract_age_days: 0,
7953
+ total_transfers: 0,
7954
+ owner_balance_pct: goplus.ownerPercent ?? 0,
7955
+ is_open_source: goplus.isOpenSource ? 1 : 0,
7956
+ top10_holder_pct: 0
7957
+ }) ?? null;
7958
+ }
7959
+ return {
7960
+ address,
7961
+ chain,
7962
+ ruleBasedRiskScore: indicators.riskScore,
7963
+ mlAnalysis: mlResult ?? { note: "ML sidecar not available" },
7964
+ indicators: {
7965
+ isHoneypot: indicators.isHoneypot,
7966
+ ownerCanMint: indicators.ownerCanMint,
7967
+ ownerCanPause: indicators.ownerCanPause,
7968
+ hasBlacklist: indicators.hasBlacklist,
7969
+ highSellTax: indicators.highSellTax
7970
+ },
7971
+ details: indicators.details,
7972
+ goplus: goplus ? {
7973
+ riskLevel: goplus.riskLevel,
7974
+ buyTax: goplus.buyTax,
7975
+ sellTax: goplus.sellTax,
7976
+ isHoneypot: goplus.isHoneypot,
7977
+ isMintable: goplus.isMintable,
7978
+ holderCount: goplus.holderCount
7979
+ } : null
7980
+ };
7981
+ }
7982
+ case "get_wallet_behavior": {
7983
+ const address = String(params["address"] ?? "");
7984
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
7985
+ const adapter = getAdapter(chain);
7986
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
7987
+ const analysis = await analyzeWallet(address, adapter);
7988
+ return {
7989
+ address: analysis.address,
7990
+ chain: analysis.chain,
7991
+ balance: analysis.balance.toString(),
7992
+ transactionCount: analysis.transactionCount,
7993
+ riskLevel: analysis.riskLevel,
7994
+ patterns: analysis.patterns,
7995
+ mlBehavior: analysis.mlBehavior ?? { note: "ML sidecar not available" }
7996
+ };
7997
+ }
7998
+ case "analyze_news_sentiment": {
7999
+ const symbol = String(params["symbol"] ?? "");
8000
+ let mlClient2 = getMLClient();
8001
+ if (!mlClient2) {
8002
+ try {
8003
+ const cfg = getConfig();
8004
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
8005
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
8006
+ }
8007
+ } catch {
8008
+ }
8009
+ }
8010
+ const news = await fetchCryptoNews(symbol || void 0, getConfig().cryptopanicApiKey);
8011
+ if (news.length === 0) {
8012
+ return { symbol, sentiment: "neutral", note: "No news found" };
8013
+ }
8014
+ const headlines = news.slice(0, 10).map((n) => n.title);
8015
+ if (mlClient2) {
8016
+ const results = await mlClient2.analyzeSentimentBatch(headlines);
8017
+ if (results.length > 0) {
8018
+ const avgScore = results.reduce((s, r) => s + r.score, 0) / results.length;
8019
+ const avgConf = results.reduce((s, r) => s + r.confidence, 0) / results.length;
8020
+ const allTopics = [...new Set(results.flatMap((r) => r.key_topics))];
8021
+ return {
8022
+ symbol,
8023
+ sentiment: avgScore > 0.2 ? "bullish" : avgScore < -0.2 ? "bearish" : "neutral",
8024
+ score: Math.round(avgScore * 1e3) / 1e3,
8025
+ confidence: Math.round(avgConf * 100),
8026
+ topics: allTopics.slice(0, 5),
8027
+ headlines: results.map((r, i) => ({
8028
+ title: headlines[i],
8029
+ sentiment: r.sentiment,
8030
+ score: r.score,
8031
+ confidence: r.confidence
8032
+ })),
8033
+ model: results[0]?.model ?? "unknown",
8034
+ articleCount: news.length
8035
+ };
8036
+ }
8037
+ }
8038
+ let pos = 0;
8039
+ let neg = 0;
8040
+ for (const n of news) {
8041
+ if (n.sentiment === "positive") pos++;
8042
+ else if (n.sentiment === "negative") neg++;
8043
+ }
8044
+ const score = news.length > 0 ? (pos - neg) / news.length : 0;
8045
+ return {
8046
+ symbol,
8047
+ sentiment: score > 0.2 ? "bullish" : score < -0.2 ? "bearish" : "neutral",
8048
+ score,
8049
+ confidence: 50,
8050
+ topics: [],
8051
+ headlines: headlines.map((h, i) => ({
8052
+ title: h,
8053
+ sentiment: news[i]?.sentiment ?? "neutral"
8054
+ })),
8055
+ model: "vote-count-fallback",
8056
+ articleCount: news.length
8057
+ };
8058
+ }
8059
+ case "get_market_regime": {
8060
+ const symbol = String(params["symbol"] ?? "BTC");
8061
+ const ta = await analyzeTechnicals(symbol, "4h");
8062
+ const [fgResult, fundingResult] = await Promise.allSettled([
8063
+ fetchFearGreedIndex(1),
8064
+ fetchFundingRate(symbol)
8065
+ ]);
8066
+ const fg = fgResult.status === "fulfilled" ? fgResult.value.current.value : 50;
8067
+ const funding = fundingResult.status === "fulfilled" ? fundingResult.value.fundingRate : 0;
8068
+ const price = ta.indicators.ema12 ?? 0;
8069
+ const atrVal = ta.indicators.atr ?? 0;
8070
+ const atrPct = price > 0 ? atrVal / price * 100 : 3;
8071
+ const regime = await detectMarketRegime(symbol, {
8072
+ returns_1d: 0,
8073
+ returns_7d: 0,
8074
+ volatility_14d: atrPct,
8075
+ volume_ratio: 1,
8076
+ rsi: ta.indicators.rsi ?? 50,
8077
+ bb_width: ta.indicators.bollingerBands ? (ta.indicators.bollingerBands.upper - ta.indicators.bollingerBands.lower) / ta.indicators.bollingerBands.middle * 100 : 0,
8078
+ fear_greed: fg,
8079
+ funding_rate: funding,
8080
+ price_vs_sma200: 0
8081
+ });
8082
+ return {
8083
+ symbol: symbol.toUpperCase(),
8084
+ regime: regime.regime,
8085
+ confidence: regime.confidence,
8086
+ probabilities: regime.probabilities,
8087
+ model: regime.model
8088
+ };
8089
+ }
8090
+ case "get_ta_ml_analysis": {
8091
+ const symbol = String(params["symbol"] ?? "BTC");
8092
+ const timeframe = String(params["timeframe"] ?? "4h");
8093
+ const ta = await analyzeTechnicals(symbol, timeframe);
8094
+ return {
8095
+ symbol: ta.symbol,
8096
+ timeframe: ta.timeframe,
8097
+ composite: ta.composite,
8098
+ signals: ta.signals.map((s) => ({
8099
+ name: s.name,
8100
+ signal: s.signal,
8101
+ strength: s.strength,
8102
+ description: s.description
8103
+ })),
8104
+ indicators: {
8105
+ rsi: ta.indicators.rsi ? Math.round(ta.indicators.rsi * 100) / 100 : null,
8106
+ macd: ta.indicators.macd,
8107
+ bollingerBands: ta.indicators.bollingerBands,
8108
+ ema12: ta.indicators.ema12,
8109
+ ema26: ta.indicators.ema26,
8110
+ atr: ta.indicators.atr
8111
+ },
8112
+ note: "ML-enhanced: signals and composite use learned weights when ML sidecar is available"
8113
+ };
8114
+ }
8115
+ case "get_project_risk_ml": {
8116
+ const address = String(params["address"] ?? "");
8117
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
8118
+ const adapter = getAdapter(chain);
8119
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
8120
+ const analysis = await analyzeProject(address, adapter);
8121
+ const risk = assessRisk(analysis);
8122
+ return {
8123
+ address,
8124
+ chain,
8125
+ riskScore: risk.score,
8126
+ riskLevel: risk.level,
8127
+ summary: risk.summary,
8128
+ factors: risk.factors,
8129
+ mlScore: risk.mlScore ?? null,
8130
+ mlLevel: risk.mlLevel ?? null,
8131
+ token: analysis.token ? {
8132
+ name: analysis.token.name,
8133
+ symbol: analysis.token.symbol,
8134
+ decimals: analysis.token.decimals
8135
+ } : null,
8136
+ holderConcentration: analysis.holderConcentration,
8137
+ contractVerified: analysis.contractVerified
8138
+ };
8139
+ }
8140
+ case "get_portfolio_forecast": {
8141
+ const agentName = String(params["agentName"] ?? "");
8142
+ const agent = getAgentByName(agentName);
8143
+ if (!agent) return { error: `Agent "${agentName}" not found` };
8144
+ let mlClient2 = getMLClient();
8145
+ if (!mlClient2) {
8146
+ try {
8147
+ const cfg = getConfig();
8148
+ if (cfg.ml?.enabled && cfg.ml.sidecarUrl) {
8149
+ mlClient2 = initMLClient(cfg.ml.sidecarUrl);
8150
+ }
8151
+ } catch {
8152
+ }
8153
+ }
8154
+ if (!mlClient2) {
8155
+ return {
8156
+ agentName,
8157
+ error: "ML sidecar not available for portfolio forecast"
8158
+ };
8159
+ }
8160
+ const state = getAgentStatus(agent.id);
8161
+ return {
8162
+ agentName,
8163
+ status: state?.status ?? "idle",
8164
+ cycleCount: state?.cycleCount ?? 0,
8165
+ note: "Portfolio forecast requires trade history. Use calculateMetricsWithForecast() in agent engine for full predictions."
8166
+ };
8167
+ }
7138
8168
  case "create_agent": {
7139
8169
  const agentName = String(params["name"] ?? "");
7140
8170
  const strategy = String(params["strategy"] ?? "momentum");
@@ -7211,6 +8241,9 @@ var init_tool_handler = __esm({
7211
8241
  init_fear_greed();
7212
8242
  init_technical_analysis();
7213
8243
  init_predictor();
8244
+ init_regime();
8245
+ init_project_analyzer();
8246
+ init_risk_scorer();
7214
8247
  init_agent();
7215
8248
  init_client();
7216
8249
  init_feature_engineer();
@@ -7504,6 +8537,120 @@ var init_tools = __esm({
7504
8537
  required: []
7505
8538
  }
7506
8539
  },
8540
+ {
8541
+ name: "get_rug_ml_analysis",
8542
+ description: "Run ML-powered rug pull analysis on a token. Uses Gradient Boosted classifier trained on historical rug patterns to predict rug probability, risk level, and key risk factors. Enhanced version of check_rug_indicators with ML scoring.",
8543
+ input_schema: {
8544
+ type: "object",
8545
+ properties: {
8546
+ address: {
8547
+ type: "string",
8548
+ description: "The token contract address to analyze."
8549
+ },
8550
+ chain: {
8551
+ type: "string",
8552
+ description: "The blockchain the token is deployed on."
8553
+ }
8554
+ },
8555
+ required: ["address", "chain"]
8556
+ }
8557
+ },
8558
+ {
8559
+ name: "get_wallet_behavior",
8560
+ description: "ML-powered wallet behavior classification. Uses LSTM model to classify a wallet as: normal_trader, bot, whale, sniper, mev_bot, mixer_user, or rug_deployer. Returns behavior type, confidence, risk score, and behavioral indicators.",
8561
+ input_schema: {
8562
+ type: "object",
8563
+ properties: {
8564
+ address: {
8565
+ type: "string",
8566
+ description: "The wallet address to classify."
8567
+ },
8568
+ chain: {
8569
+ type: "string",
8570
+ description: "The blockchain the wallet is on."
8571
+ }
8572
+ },
8573
+ required: ["address", "chain"]
8574
+ }
8575
+ },
8576
+ {
8577
+ name: "analyze_news_sentiment",
8578
+ description: "ML-powered NLP sentiment analysis on crypto news. Uses DistilBERT model to analyze news headlines for a token, returning bullish/bearish/neutral sentiment, confidence score, and detected topics (regulation, defi, security, etc.).",
8579
+ input_schema: {
8580
+ type: "object",
8581
+ properties: {
8582
+ symbol: {
8583
+ type: "string",
8584
+ description: 'Token symbol to analyze news sentiment for (e.g. "BTC", "ETH").'
8585
+ }
8586
+ },
8587
+ required: ["symbol"]
8588
+ }
8589
+ },
8590
+ {
8591
+ name: "get_market_regime",
8592
+ description: "Detect the current market regime using ML Hidden Markov Model. Returns regime type (trending_bull, trending_bear, ranging, volatile, capitulation), confidence, and probability distribution across all regimes. Uses HMM when available, falls back to heuristic analysis.",
8593
+ input_schema: {
8594
+ type: "object",
8595
+ properties: {
8596
+ symbol: {
8597
+ type: "string",
8598
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
8599
+ }
8600
+ },
8601
+ required: ["symbol"]
8602
+ }
8603
+ },
8604
+ {
8605
+ name: "get_ta_ml_analysis",
8606
+ description: "Run ML-enhanced technical analysis with learned signal weights. Uses Random Forest to interpret RSI, MACD, Bollinger Bands, EMA crossover, ATR, OBV simultaneously. Returns signals with ML-derived importance weights and composite direction. More accurate than static weight TA.",
8607
+ input_schema: {
8608
+ type: "object",
8609
+ properties: {
8610
+ symbol: {
8611
+ type: "string",
8612
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
8613
+ },
8614
+ timeframe: {
8615
+ type: "string",
8616
+ description: 'Kline interval: "1h", "4h", "1d". Defaults to "4h".'
8617
+ }
8618
+ },
8619
+ required: ["symbol"]
8620
+ }
8621
+ },
8622
+ {
8623
+ name: "get_project_risk_ml",
8624
+ description: "Run ML-powered project risk scoring on a token. Uses GBM classifier trained on contract features (verification, holder concentration, taxes, mint/pause/blacklist capabilities) to predict overall project risk probability and identify top risk factors.",
8625
+ input_schema: {
8626
+ type: "object",
8627
+ properties: {
8628
+ address: {
8629
+ type: "string",
8630
+ description: "The token contract address to analyze."
8631
+ },
8632
+ chain: {
8633
+ type: "string",
8634
+ description: "The blockchain the token is deployed on."
8635
+ }
8636
+ },
8637
+ required: ["address", "chain"]
8638
+ }
8639
+ },
8640
+ {
8641
+ name: "get_portfolio_forecast",
8642
+ description: "Generate forward-looking portfolio performance predictions using ML. Analyzes trade history to predict next-period return, Sharpe ratio, and max drawdown. Requires at least 10 completed trades.",
8643
+ input_schema: {
8644
+ type: "object",
8645
+ properties: {
8646
+ agentName: {
8647
+ type: "string",
8648
+ description: "The trading agent name to forecast for."
8649
+ }
8650
+ },
8651
+ required: ["agentName"]
8652
+ }
8653
+ },
7507
8654
  {
7508
8655
  name: "create_agent",
7509
8656
  description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
@@ -9722,9 +10869,19 @@ var init_price_ticker = __esm({
9722
10869
  import { Box as Box3, Text as Text3 } from "ink";
9723
10870
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
9724
10871
  function WelcomeBanner() {
9725
- return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, children: [
9726
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "blue", children: "vizzor" }),
9727
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " v0.1.0 \u2014 crypto chronovisor. Type /help for commands." })
10872
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
10873
+ /* @__PURE__ */ jsxs3(Box3, { children: [
10874
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: " " }),
10875
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "blue", children: "vizzor" }),
10876
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: " v0.10.0" }),
10877
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " \u2014 AI-powered crypto chronovisor" })
10878
+ ] }),
10879
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " ML models: LSTM + Random Forest + Isolation Forest + GBM Rug + Wallet LSTM + DistilBERT NLP" }) }),
10880
+ /* @__PURE__ */ jsxs3(Box3, { children: [
10881
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " Type " }),
10882
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "/help" }),
10883
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " for commands or ask anything about crypto." })
10884
+ ] })
9728
10885
  ] });
9729
10886
  }
9730
10887
  var init_welcome_banner = __esm({
@@ -11408,7 +12565,7 @@ collectCmd.command("status").description("Show data collection status").action(a
11408
12565
  const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
11409
12566
  handleCollectStatus2();
11410
12567
  });
11411
- program.command("serve").description("Start the REST API server").option("--port <port>", "Server port", parseInt, 3e3).option("--host <host>", "Server host", "0.0.0.0").option("--auth", "Enable API key authentication", false).action(async (options) => {
12568
+ program.command("serve").description("Start the REST API server").option("--port <port>", "Server port", (v) => parseInt(v, 10), 3e3).option("--host <host>", "Server host", "0.0.0.0").option("--auth", "Enable API key authentication", false).action(async (options) => {
11412
12569
  const { handleServe: handleServe2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
11413
12570
  await handleServe2(options);
11414
12571
  });