@vizzor/cli 0.9.0 → 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
@@ -139,6 +139,30 @@ function loadConfig() {
139
139
  }
140
140
  raw["ai"]["provider"] = process.env["VIZZOR_AI_PROVIDER"];
141
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
+ }
142
166
  const config2 = vizzorConfigSchema.parse(raw);
143
167
  cachedConfig = config2;
144
168
  return config2;
@@ -176,7 +200,10 @@ function saveConfigValue(key, value) {
176
200
  if (!raw[section] || typeof raw[section] !== "object") {
177
201
  raw[section] = {};
178
202
  }
179
- 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;
180
207
  raw[section][field] = parsed;
181
208
  } else {
182
209
  raw[key] = value;
@@ -207,7 +234,18 @@ var init_loader = __esm({
207
234
  "ai.provider": { env: "VIZZOR_AI_PROVIDER", nested: "ai" },
208
235
  "ai.model": { env: "VIZZOR_AI_MODEL", nested: "ai" },
209
236
  "ai.maxTokens": { env: "VIZZOR_AI_MAX_TOKENS", nested: "ai" },
210
- "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" }
211
249
  };
212
250
  }
213
251
  });
@@ -830,6 +868,329 @@ var init_registry = __esm({
830
868
  }
831
869
  });
832
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
+
833
1194
  // src/core/scanner/project-analyzer.ts
834
1195
  async function analyzeProject(address, adapter) {
835
1196
  const [token, code, holders] = await Promise.allSettled([
@@ -848,17 +1209,48 @@ async function analyzeProject(address, adapter) {
848
1209
  );
849
1210
  const totalSupply = tokenInfo?.totalSupply ?? 0n;
850
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
+ }
851
1245
  return {
852
1246
  token: tokenInfo,
853
1247
  contractVerified: hasSourceCode,
854
1248
  hasSourceCode,
855
1249
  holderConcentration: topHolderPercentage,
856
- topHolders: topHolders.map((h) => ({
857
- address: h.address,
858
- percentage: totalSupply > 0n ? Number(h.balance * 10000n / totalSupply) / 100 : 0
859
- })),
1250
+ topHolders: topHoldersMapped,
860
1251
  riskIndicators,
861
- riskScore: Math.min(100, riskScore)
1252
+ riskScore: Math.min(100, riskScore),
1253
+ mlRisk
862
1254
  };
863
1255
  }
864
1256
  function evaluateRiskIndicators(token, hasSource, topHolders) {
@@ -893,16 +1285,30 @@ function evaluateRiskIndicators(token, hasSource, topHolders) {
893
1285
  var init_project_analyzer = __esm({
894
1286
  "src/core/scanner/project-analyzer.ts"() {
895
1287
  "use strict";
1288
+ init_client();
896
1289
  }
897
1290
  });
898
1291
 
899
1292
  // src/core/scanner/risk-scorer.ts
900
1293
  function assessRisk(analysis) {
901
- const { riskScore, riskIndicators } = analysis;
902
- 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);
903
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
+ }
904
1303
  const summary = buildSummary(level2, factors.length);
905
- 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
+ };
906
1312
  }
907
1313
  function getRiskLevel(score) {
908
1314
  if (score <= 20) return "low";
@@ -1138,6 +1544,7 @@ var init_market = __esm({
1138
1544
  "src/core/trends/market.ts"() {
1139
1545
  "use strict";
1140
1546
  init_dexscreener();
1547
+ init_client();
1141
1548
  }
1142
1549
  });
1143
1550
 
@@ -1193,20 +1600,47 @@ async function analyzeSentiment(query) {
1193
1600
  }
1194
1601
  const news = await fetchCryptoNews(query, apiToken);
1195
1602
  if (news.length > 0) {
1196
- let positiveCount = 0;
1197
- let negativeCount = 0;
1198
- for (const article of news) {
1199
- if (article.sentiment === "positive") positiveCount++;
1200
- 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);
1201
1633
  }
1202
1634
  const total = news.length;
1203
- const newsScore = total > 0 ? (positiveCount - negativeCount) / total : 0;
1204
1635
  sources.push({
1205
- source: "CryptoPanic News",
1636
+ source: mlSentiment ? "ML NLP Sentiment" : "CryptoPanic News",
1206
1637
  score: newsScore,
1207
1638
  volume: total,
1208
1639
  trending: total > 5,
1209
- 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
1210
1644
  });
1211
1645
  }
1212
1646
  } catch {
@@ -1250,12 +1684,23 @@ async function analyzeSentiment(query) {
1250
1684
  }
1251
1685
  return { overall, sources, consensus };
1252
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
+ }
1253
1697
  var init_sentiment = __esm({
1254
1698
  "src/core/trends/sentiment.ts"() {
1255
1699
  "use strict";
1256
1700
  init_cryptopanic();
1257
1701
  init_dexscreener();
1258
1702
  init_loader();
1703
+ init_client();
1259
1704
  }
1260
1705
  });
1261
1706
 
@@ -1540,15 +1985,74 @@ async function analyzeTechnicals(symbol, timeframe = "4h") {
1540
1985
  }
1541
1986
  const atr = calculateATR(highs, lows, closes);
1542
1987
  if (atr !== null) {
1543
- const currentPrice = closes[closes.length - 1];
1544
- signals.push(interpretATR(atr, currentPrice));
1988
+ const currentPrice2 = closes[closes.length - 1];
1989
+ signals.push(interpretATR(atr, currentPrice2));
1545
1990
  }
1546
1991
  const obv = calculateOBV(closes, volumes);
1547
1992
  if (obv !== null) {
1548
- const priceChange = closes.length >= 2 ? closes[closes.length - 1] - closes[closes.length - 2] : 0;
1549
- 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));
1550
1995
  }
1551
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
+ }
1552
2056
  const composite = calculateComposite(signals);
1553
2057
  return {
1554
2058
  symbol: symbol.toUpperCase(),
@@ -1559,8 +2063,8 @@ async function analyzeTechnicals(symbol, timeframe = "4h") {
1559
2063
  rsi,
1560
2064
  macd,
1561
2065
  bollingerBands: bb,
1562
- ema12: ema12.length > 0 ? ema12[ema12.length - 1] : null,
1563
- ema26: ema26.length > 0 ? ema26[ema26.length - 1] : null,
2066
+ ema12: ema12Val || null,
2067
+ ema26: ema26Val || null,
1564
2068
  sma20: sma20.length > 0 ? sma20[sma20.length - 1] : null,
1565
2069
  atr,
1566
2070
  obv
@@ -1741,6 +2245,7 @@ var init_analyzer = __esm({
1741
2245
  "use strict";
1742
2246
  init_binance();
1743
2247
  init_indicators();
2248
+ init_client();
1744
2249
  WEIGHTS = {
1745
2250
  RSI: 20,
1746
2251
  MACD: 20,
@@ -1800,46 +2305,6 @@ var init_fear_greed = __esm({
1800
2305
  }
1801
2306
  });
1802
2307
 
1803
- // src/utils/logger.ts
1804
- import pino from "pino";
1805
- function createLogger(name) {
1806
- const isDev = process.env["NODE_ENV"] !== "production";
1807
- if (isDev) {
1808
- return pino({
1809
- name,
1810
- level,
1811
- transport: {
1812
- target: "pino-pretty",
1813
- options: {
1814
- colorize: true
1815
- }
1816
- }
1817
- });
1818
- }
1819
- return pino({ name, level });
1820
- }
1821
- var level;
1822
- var init_logger = __esm({
1823
- "src/utils/logger.ts"() {
1824
- "use strict";
1825
- level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
1826
- }
1827
- });
1828
-
1829
- // src/ml/client.ts
1830
- function getMLClient() {
1831
- return mlClient;
1832
- }
1833
- var log, mlClient;
1834
- var init_client = __esm({
1835
- "src/ml/client.ts"() {
1836
- "use strict";
1837
- init_logger();
1838
- log = createLogger("ml-client");
1839
- mlClient = null;
1840
- }
1841
- });
1842
-
1843
2308
  // src/ml/feature-engineer.ts
1844
2309
  async function buildFeatureVector(symbol) {
1845
2310
  const [ta, fundingResult, tickerResult, fgResult, klines] = await Promise.allSettled([
@@ -2035,7 +2500,16 @@ async function generatePrediction(symbol) {
2035
2500
  composite: Math.round(composite),
2036
2501
  disclaimer: "This is not financial advice. Predictions are based on historical data and AI analysis. Always do your own research."
2037
2502
  };
2038
- 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
+ }
2039
2513
  if (mlClient2) {
2040
2514
  try {
2041
2515
  const features = await buildFeatureVector(symbol);
@@ -2074,6 +2548,7 @@ var init_predictor = __esm({
2074
2548
  init_binance();
2075
2549
  init_client();
2076
2550
  init_feature_engineer();
2551
+ init_loader();
2077
2552
  WEIGHTS2 = {
2078
2553
  technical: 40,
2079
2554
  sentiment: 20,
@@ -2162,7 +2637,39 @@ async function analyzeWallet(address, adapter) {
2162
2637
  ]);
2163
2638
  const walletBalance = balance.status === "fulfilled" ? balance.value : 0n;
2164
2639
  const txHistory = transactions.status === "fulfilled" ? transactions.value : [];
2165
- 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
+ }
2166
2673
  return {
2167
2674
  address,
2168
2675
  chain: adapter.chainId,
@@ -2170,23 +2677,129 @@ async function analyzeWallet(address, adapter) {
2170
2677
  transactionCount: txHistory.length,
2171
2678
  tokenBalances: [],
2172
2679
  patterns,
2173
- 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
2174
2735
  };
2175
2736
  }
2176
- function detectPatterns(txCount) {
2737
+ function detectPatterns(txHistory) {
2177
2738
  const patterns = [];
2739
+ const txCount = txHistory.length;
2178
2740
  if (txCount === 0) {
2179
2741
  patterns.push({
2180
2742
  type: "new_wallet",
2181
2743
  description: "Wallet has no transaction history",
2182
2744
  severity: "info"
2183
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
+ });
2184
2795
  }
2185
2796
  return patterns;
2186
2797
  }
2187
2798
  var init_wallet_analyzer = __esm({
2188
2799
  "src/core/forensics/wallet-analyzer.ts"() {
2189
2800
  "use strict";
2801
+ init_client();
2802
+ init_loader();
2190
2803
  }
2191
2804
  });
2192
2805
 
@@ -2771,8 +3384,6 @@ async function handleConfigInit() {
2771
3384
  console.log(` 3. Try scanning a project: ${chalk6.cyan("vizzor scan ethereum")}`);
2772
3385
  }
2773
3386
  async function handleConfigSet(key, value) {
2774
- const configDir = getConfigDir();
2775
- const configPath = resolve(configDir, "config.yaml");
2776
3387
  const isSensitive = key.toLowerCase().includes("key") || key.toLowerCase().includes("token");
2777
3388
  if (isSensitive) {
2778
3389
  const error = validateKey(key, value);
@@ -2784,20 +3395,12 @@ async function handleConfigSet(key, value) {
2784
3395
  console.log(chalk6.yellow(error));
2785
3396
  }
2786
3397
  }
2787
- await loadConfig();
2788
- const config2 = getConfig();
2789
- const updatedConfig = JSON.parse(JSON.stringify(config2));
2790
- if (key.includes(".")) {
2791
- const [section, field] = key.split(".");
2792
- if (!updatedConfig[section] || typeof updatedConfig[section] !== "object") {
2793
- updatedConfig[section] = {};
2794
- }
2795
- const parsed = field === "maxTokens" ? Number(value) : value;
2796
- updatedConfig[section][field] = parsed;
2797
- } else {
2798
- 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;
2799
3403
  }
2800
- writeFileSync2(configPath, yamlStringify(updatedConfig), "utf-8");
2801
3404
  const displayValue = isSensitive ? maskKey(value) : value;
2802
3405
  console.log(chalk6.green(`Set ${key} = ${displayValue}`));
2803
3406
  }
@@ -3780,10 +4383,25 @@ var init_goplus = __esm({
3780
4383
  async function buildContextBlock(userMessage) {
3781
4384
  const lower = userMessage.toLowerCase();
3782
4385
  const sections = [];
3783
- 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;
3784
4402
  const mentionedTokens = detectTokens(lower);
3785
4403
  const unknownTokens = detectUnknownTokens(userMessage, mentionedTokens);
3786
- const isAnalysisQuery = matchesAny(lower, ANALYSIS_KEYWORDS);
4404
+ const isAnalysisQuery = matchesAny(lower, ANALYSIS_KEYWORDS) || mlIntent === "analysis";
3787
4405
  const tasks = [];
3788
4406
  if (mentionedTokens.length > 0 || addressMatch || matchesAny(lower, PRICE_KEYWORDS)) {
3789
4407
  tasks.push(
@@ -3809,8 +4427,7 @@ async function buildContextBlock(userMessage) {
3809
4427
  }
3810
4428
  }
3811
4429
  if (addressMatch && isAnalysisQuery) {
3812
- const cfg = getConfig();
3813
- const chain = cfg.defaultChain || "ethereum";
4430
+ const chain = detectedChain || getConfig().defaultChain || "ethereum";
3814
4431
  tasks.push(
3815
4432
  fetchSecurityData(addressMatch[0], chain).then((data) => {
3816
4433
  if (data) sections.push(data);
@@ -3851,8 +4468,9 @@ async function buildContextBlock(userMessage) {
3851
4468
  })
3852
4469
  );
3853
4470
  }
3854
- const isBroadQuery = matchesAny(lower, BROAD_KEYWORDS);
3855
- 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) {
3856
4474
  if (!matchesAny(lower, TRENDING_KEYWORDS)) {
3857
4475
  tasks.push(
3858
4476
  fetchTrendingData().then((data) => {
@@ -3874,9 +4492,18 @@ async function buildContextBlock(userMessage) {
3874
4492
  })
3875
4493
  );
3876
4494
  }
3877
- 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();
3878
4505
  tasks.push(
3879
- fetchTokenData(["bitcoin", "ethereum", "solana"]).then((data) => {
4506
+ fetchNewsData(symbol).then((data) => {
3880
4507
  if (data) sections.push(data);
3881
4508
  })
3882
4509
  );
@@ -4158,8 +4785,30 @@ async function fetchNewsData(symbol) {
4158
4785
  try {
4159
4786
  const news = await fetchCryptoNews(symbol, getConfig().cryptopanicApiKey);
4160
4787
  if (news.length > 0) {
4788
+ const headlines = news.slice(0, 8);
4161
4789
  const lines = [`## Latest Crypto News${symbol ? ` (${symbol})` : ""}`];
4162
- 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) {
4163
4812
  lines.push(
4164
4813
  `- [${n.sentiment.toUpperCase()}] ${n.title} (${n.source.title}, ${n.publishedAt})`
4165
4814
  );
@@ -4198,7 +4847,7 @@ async function fetchRaisesData() {
4198
4847
  if (raises.length === 0) return null;
4199
4848
  const lines = ["## Recent Crypto Fundraising Rounds (last 30 days)"];
4200
4849
  for (const r of raises.slice(0, 10)) {
4201
- 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";
4202
4851
  const date = new Date(r.date * 1e3).toISOString().split("T")[0];
4203
4852
  lines.push(
4204
4853
  `- ${r.name} \u2014 ${r.round} (${amount}) on ${r.chains.join(", ") || "multi-chain"} [${date}]${r.leadInvestors.length > 0 ? ` Led by: ${r.leadInvestors.join(", ")}` : ""}`
@@ -5135,6 +5784,7 @@ var init_context_injector = __esm({
5135
5784
  init_goplus();
5136
5785
  init_loader();
5137
5786
  init_constants();
5787
+ init_client();
5138
5788
  PRICE_KEYWORDS = ["price", "worth", "cost", "value", "how much"];
5139
5789
  TRENDING_KEYWORDS = ["trending", "hot", "popular", "top", "best", "hype"];
5140
5790
  NEWS_KEYWORDS = ["news", "latest", "update", "happening", "announcement"];
@@ -5514,6 +6164,7 @@ var DANGEROUS_FUNCTIONS, DANGEROUS_OPCODES;
5514
6164
  var init_bytecode_scanner = __esm({
5515
6165
  "src/core/forensics/bytecode-scanner.ts"() {
5516
6166
  "use strict";
6167
+ init_client();
5517
6168
  DANGEROUS_FUNCTIONS = [
5518
6169
  // Mint functions — inflation risk
5519
6170
  {
@@ -5671,6 +6322,42 @@ async function detectRugIndicators(tokenAddress, adapter) {
5671
6322
  }
5672
6323
  }
5673
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
+ }
5674
6361
  return {
5675
6362
  isHoneypot: canPause || hasBlacklist_,
5676
6363
  hasLiquidityLock: false,
@@ -5678,8 +6365,9 @@ async function detectRugIndicators(tokenAddress, adapter) {
5678
6365
  ownerCanPause: canPause,
5679
6366
  hasBlacklist: hasBlacklist_,
5680
6367
  highSellTax: false,
5681
- riskScore,
5682
- details
6368
+ riskScore: mlAnalysis ? Math.round(riskScore * 0.4 + mlAnalysis.rug_probability * 100 * 0.6) : riskScore,
6369
+ details,
6370
+ mlAnalysis
5683
6371
  };
5684
6372
  }
5685
6373
  function calculateRugRisk(details) {
@@ -5705,6 +6393,83 @@ var init_rug_detector = __esm({
5705
6393
  "src/core/forensics/rug-detector.ts"() {
5706
6394
  "use strict";
5707
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();
5708
6473
  }
5709
6474
  });
5710
6475
 
@@ -5895,6 +6660,7 @@ var momentumStrategy;
5895
6660
  var init_momentum = __esm({
5896
6661
  "src/core/agent/strategies/momentum.ts"() {
5897
6662
  "use strict";
6663
+ init_client();
5898
6664
  momentumStrategy = {
5899
6665
  name: "momentum",
5900
6666
  description: "RSI reversal + MACD confirmation. Buy oversold bounces, sell overbought peaks.",
@@ -5970,6 +6736,7 @@ var trendFollowingStrategy;
5970
6736
  var init_trend_following = __esm({
5971
6737
  "src/core/agent/strategies/trend-following.ts"() {
5972
6738
  "use strict";
6739
+ init_client();
5973
6740
  trendFollowingStrategy = {
5974
6741
  name: "trend-following",
5975
6742
  description: "EMA crossover trend detection. Buy golden cross, sell death cross.",
@@ -6128,7 +6895,7 @@ var init_ml_adaptive = __esm({
6128
6895
  "src/core/agent/strategies/ml-adaptive.ts"() {
6129
6896
  "use strict";
6130
6897
  init_client();
6131
- init_feature_engineer();
6898
+ init_regime();
6132
6899
  mlAdaptiveStrategy = {
6133
6900
  name: "ml-adaptive",
6134
6901
  description: "ML-powered strategy using contextual bandit approach. Uses LSTM/RF predictions when available, falls back to rule-based when < 100 training samples.",
@@ -7078,7 +7845,16 @@ async function handleTool(name, input) {
7078
7845
  }
7079
7846
  case "get_ml_prediction": {
7080
7847
  const symbol = String(params["symbol"] ?? "BTC");
7081
- 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
+ }
7082
7858
  if (!mlClient2) {
7083
7859
  const prediction = await generatePrediction(symbol);
7084
7860
  return {
@@ -7139,6 +7915,256 @@ async function handleTool(name, input) {
7139
7915
  }
7140
7916
  };
7141
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
+ }
7142
8168
  case "create_agent": {
7143
8169
  const agentName = String(params["name"] ?? "");
7144
8170
  const strategy = String(params["strategy"] ?? "momentum");
@@ -7215,6 +8241,9 @@ var init_tool_handler = __esm({
7215
8241
  init_fear_greed();
7216
8242
  init_technical_analysis();
7217
8243
  init_predictor();
8244
+ init_regime();
8245
+ init_project_analyzer();
8246
+ init_risk_scorer();
7218
8247
  init_agent();
7219
8248
  init_client();
7220
8249
  init_feature_engineer();
@@ -7508,6 +8537,120 @@ var init_tools = __esm({
7508
8537
  required: []
7509
8538
  }
7510
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
+ },
7511
8654
  {
7512
8655
  name: "create_agent",
7513
8656
  description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
@@ -9726,9 +10869,19 @@ var init_price_ticker = __esm({
9726
10869
  import { Box as Box3, Text as Text3 } from "ink";
9727
10870
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
9728
10871
  function WelcomeBanner() {
9729
- return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, children: [
9730
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "blue", children: "vizzor" }),
9731
- /* @__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
+ ] })
9732
10885
  ] });
9733
10886
  }
9734
10887
  var init_welcome_banner = __esm({
@@ -11412,7 +12565,7 @@ collectCmd.command("status").description("Show data collection status").action(a
11412
12565
  const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
11413
12566
  handleCollectStatus2();
11414
12567
  });
11415
- 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) => {
11416
12569
  const { handleServe: handleServe2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
11417
12570
  await handleServe2(options);
11418
12571
  });