@vizzor/cli 0.5.0 → 0.7.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
@@ -50,6 +50,26 @@ var init_schema = __esm({
50
50
  type: z.enum(["sqlite", "postgres"]).default("sqlite"),
51
51
  url: z.string().optional()
52
52
  }).default(() => ({ type: "sqlite" })),
53
+ ml: z.object({
54
+ enabled: z.boolean().default(false),
55
+ sidecarUrl: z.string().default("http://localhost:8000"),
56
+ fallbackToRules: z.boolean().default(true)
57
+ }).default(() => ({
58
+ enabled: false,
59
+ sidecarUrl: "http://localhost:8000",
60
+ fallbackToRules: true
61
+ })),
62
+ api: z.object({
63
+ port: z.number().default(3e3),
64
+ host: z.string().default("0.0.0.0"),
65
+ enableAuth: z.boolean().default(false),
66
+ corsOrigin: z.string().default("*")
67
+ }).default(() => ({
68
+ port: 3e3,
69
+ host: "0.0.0.0",
70
+ enableAuth: false,
71
+ corsOrigin: "*"
72
+ })),
53
73
  discordToken: z.string().optional(),
54
74
  discordGuildId: z.string().optional(),
55
75
  telegramToken: z.string().optional()
@@ -647,12 +667,12 @@ var init_adapter = __esm({
647
667
  toBlock: options?.toBlock,
648
668
  args: options?.args
649
669
  });
650
- return logs.map((log2) => ({
651
- eventName: log2.eventName ?? eventName,
652
- blockNumber: log2.blockNumber ?? 0n,
653
- transactionHash: log2.transactionHash ?? "0x",
654
- args: log2.args ?? {},
655
- logIndex: log2.logIndex ?? 0
670
+ return logs.map((log4) => ({
671
+ eventName: log4.eventName ?? eventName,
672
+ blockNumber: log4.blockNumber ?? 0n,
673
+ transactionHash: log4.transactionHash ?? "0x",
674
+ args: log4.args ?? {},
675
+ logIndex: log4.logIndex ?? 0
656
676
  }));
657
677
  }
658
678
  // ── Tokens ──────────────────────────────────────────────────────────────
@@ -1714,6 +1734,111 @@ var init_fear_greed = __esm({
1714
1734
  }
1715
1735
  });
1716
1736
 
1737
+ // src/utils/logger.ts
1738
+ import pino from "pino";
1739
+ function createLogger(name) {
1740
+ const isDev = process.env["NODE_ENV"] !== "production";
1741
+ if (isDev) {
1742
+ return pino({
1743
+ name,
1744
+ level,
1745
+ transport: {
1746
+ target: "pino-pretty",
1747
+ options: {
1748
+ colorize: true
1749
+ }
1750
+ }
1751
+ });
1752
+ }
1753
+ return pino({ name, level });
1754
+ }
1755
+ var level;
1756
+ var init_logger = __esm({
1757
+ "src/utils/logger.ts"() {
1758
+ "use strict";
1759
+ level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
1760
+ }
1761
+ });
1762
+
1763
+ // src/ml/client.ts
1764
+ function getMLClient() {
1765
+ return mlClient;
1766
+ }
1767
+ var log, mlClient;
1768
+ var init_client = __esm({
1769
+ "src/ml/client.ts"() {
1770
+ "use strict";
1771
+ init_logger();
1772
+ log = createLogger("ml-client");
1773
+ mlClient = null;
1774
+ }
1775
+ });
1776
+
1777
+ // src/ml/feature-engineer.ts
1778
+ async function buildFeatureVector(symbol) {
1779
+ const [ta, fundingResult, tickerResult, fgResult, klines] = await Promise.allSettled([
1780
+ analyzeTechnicals(symbol, "4h"),
1781
+ fetchFundingRate(symbol),
1782
+ fetchTickerPrice(symbol),
1783
+ fetchFearGreedIndex(1),
1784
+ fetchKlines(symbol, "4h", 100)
1785
+ ]);
1786
+ const indicators = ta.status === "fulfilled" ? ta.value.indicators : null;
1787
+ const funding = fundingResult.status === "fulfilled" ? fundingResult.value : null;
1788
+ const ticker = tickerResult.status === "fulfilled" ? tickerResult.value : null;
1789
+ const fg = fgResult.status === "fulfilled" ? fgResult.value : null;
1790
+ const candles = klines.status === "fulfilled" ? klines.value : [];
1791
+ let rsiSlope = 0;
1792
+ if (candles.length >= 17) {
1793
+ const closes = candles.map((k) => k.close);
1794
+ const recentRsi = calculateRSI(closes, 14);
1795
+ const olderCloses = closes.slice(0, -3);
1796
+ const olderRsi = calculateRSI(olderCloses, 14);
1797
+ if (recentRsi !== null && olderRsi !== null) {
1798
+ rsiSlope = recentRsi - olderRsi;
1799
+ }
1800
+ }
1801
+ let volumeRatio = 1;
1802
+ if (candles.length >= 21) {
1803
+ const currentVolume = candles[candles.length - 1].volume;
1804
+ const avgVolume = candles.slice(-21, -1).reduce((sum, k) => sum + k.volume, 0) / 20;
1805
+ volumeRatio = avgVolume > 0 ? currentVolume / avgVolume : 1;
1806
+ }
1807
+ const price = ticker?.price ?? candles[candles.length - 1]?.close ?? 0;
1808
+ const ema12 = indicators?.ema12 ?? 0;
1809
+ const ema26 = indicators?.ema26 ?? 0;
1810
+ const emaCrossoverPct = price > 0 ? (ema12 - ema26) / price * 100 : 0;
1811
+ const atr = indicators?.atr ?? 0;
1812
+ const atrPct = price > 0 ? atr / price * 100 : 0;
1813
+ return {
1814
+ rsi: indicators?.rsi ?? 50,
1815
+ macdHistogram: indicators?.macd?.histogram ?? 0,
1816
+ bollingerPercentB: indicators?.bollingerBands?.percentB ?? 0.5,
1817
+ ema12,
1818
+ ema26,
1819
+ atr,
1820
+ obv: indicators?.obv ?? 0,
1821
+ fundingRate: funding?.fundingRate ?? 0,
1822
+ fearGreed: fg?.current.value ?? 50,
1823
+ priceChange24h: ticker?.change24h ?? 0,
1824
+ rsiSlope,
1825
+ volumeRatio,
1826
+ emaCrossoverPct,
1827
+ atrPct,
1828
+ symbol: symbol.toUpperCase(),
1829
+ timestamp: Date.now()
1830
+ };
1831
+ }
1832
+ var init_feature_engineer = __esm({
1833
+ "src/ml/feature-engineer.ts"() {
1834
+ "use strict";
1835
+ init_technical_analysis();
1836
+ init_binance();
1837
+ init_fear_greed();
1838
+ init_indicators();
1839
+ }
1840
+ });
1841
+
1717
1842
  // src/core/trends/predictor.ts
1718
1843
  async function generatePrediction(symbol) {
1719
1844
  const reasoning = [];
@@ -1834,7 +1959,7 @@ async function generatePrediction(symbol) {
1834
1959
  const negativeCount = signalValues.filter((v) => v < 0).length;
1835
1960
  const agreement = Math.max(positiveCount, negativeCount) / Math.max(1, positiveCount + negativeCount);
1836
1961
  const confidence = Math.round(Math.min(95, completeness / 5 * agreement * 100));
1837
- return {
1962
+ const rulePrediction = {
1838
1963
  symbol: symbol.toUpperCase(),
1839
1964
  direction,
1840
1965
  confidence,
@@ -1844,6 +1969,34 @@ async function generatePrediction(symbol) {
1844
1969
  composite: Math.round(composite),
1845
1970
  disclaimer: "This is not financial advice. Predictions are based on historical data and AI analysis. Always do your own research."
1846
1971
  };
1972
+ const mlClient2 = getMLClient();
1973
+ if (mlClient2) {
1974
+ try {
1975
+ const features = await buildFeatureVector(symbol);
1976
+ const mlPred = await mlClient2.predict(features);
1977
+ if (mlPred) {
1978
+ return mergePredictions(rulePrediction, mlPred);
1979
+ }
1980
+ } catch {
1981
+ }
1982
+ }
1983
+ return rulePrediction;
1984
+ }
1985
+ function mergePredictions(rule, ml) {
1986
+ const mlComposite = ml.direction === "up" ? ml.probability * 100 : ml.direction === "down" ? -(ml.probability * 100) : 0;
1987
+ const mergedComposite = Math.round(rule.composite * 0.4 + mlComposite * 0.6);
1988
+ const mergedDirection = mergedComposite > 15 ? "up" : mergedComposite < -15 ? "down" : "sideways";
1989
+ const mergedConfidence = Math.round(Math.min(95, rule.confidence * 0.4 + ml.confidence * 0.6));
1990
+ return {
1991
+ ...rule,
1992
+ direction: mergedDirection,
1993
+ confidence: mergedConfidence,
1994
+ composite: mergedComposite,
1995
+ reasoning: [
1996
+ ...rule.reasoning,
1997
+ `ML (${ml.model}): ${ml.direction} with ${(ml.probability * 100).toFixed(1)}% probability (horizon: ${ml.horizon})`
1998
+ ]
1999
+ };
1847
2000
  }
1848
2001
  var WEIGHTS2;
1849
2002
  var init_predictor = __esm({
@@ -1853,6 +2006,8 @@ var init_predictor = __esm({
1853
2006
  init_sentiment();
1854
2007
  init_fear_greed();
1855
2008
  init_binance();
2009
+ init_client();
2010
+ init_feature_engineer();
1856
2011
  WEIGHTS2 = {
1857
2012
  technical: 40,
1858
2013
  sentiment: 20,
@@ -5233,7 +5388,7 @@ async function analyze(systemPrompt, userMessage, tools) {
5233
5388
  return p.analyze(systemPrompt, userMessage, tools, toolHandler);
5234
5389
  }
5235
5390
  var provider, config, toolHandler;
5236
- var init_client = __esm({
5391
+ var init_client2 = __esm({
5237
5392
  "src/ai/client.ts"() {
5238
5393
  "use strict";
5239
5394
  init_registry2();
@@ -5557,32 +5712,6 @@ var init_cache = __esm({
5557
5712
  }
5558
5713
  });
5559
5714
 
5560
- // src/utils/logger.ts
5561
- import pino from "pino";
5562
- function createLogger(name) {
5563
- const isDev = process.env["NODE_ENV"] !== "production";
5564
- if (isDev) {
5565
- return pino({
5566
- name,
5567
- level,
5568
- transport: {
5569
- target: "pino-pretty",
5570
- options: {
5571
- colorize: true
5572
- }
5573
- }
5574
- });
5575
- }
5576
- return pino({ name, level });
5577
- }
5578
- var level;
5579
- var init_logger = __esm({
5580
- "src/utils/logger.ts"() {
5581
- "use strict";
5582
- level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
5583
- }
5584
- });
5585
-
5586
5715
  // src/core/agent/engine.ts
5587
5716
  var logger, AgentEngine;
5588
5717
  var init_engine = __esm({
@@ -6053,545 +6182,1041 @@ var init_agent = __esm({
6053
6182
  }
6054
6183
  });
6055
6184
 
6056
- // src/ai/tool-handler.ts
6057
- async function handleTool(name, input) {
6058
- const params = input;
6059
- switch (name) {
6060
- case "get_token_info": {
6061
- const address = String(params["address"] ?? "");
6062
- const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6063
- const adapter = getAdapter(chain);
6064
- await adapter.connect(void 0, getConfig().etherscanApiKey);
6065
- const info = await adapter.getTokenInfo(address);
6066
- return {
6067
- address: info.address,
6068
- name: info.name,
6069
- symbol: info.symbol,
6070
- decimals: info.decimals,
6071
- totalSupply: info.totalSupply.toString()
6072
- };
6073
- }
6074
- case "analyze_wallet": {
6075
- const address = String(params["address"] ?? "");
6076
- const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6077
- const adapter = getAdapter(chain);
6078
- await adapter.connect(void 0, getConfig().etherscanApiKey);
6079
- const analysis = await analyzeWallet(address, adapter);
6080
- return {
6081
- address: analysis.address,
6082
- chain: analysis.chain,
6083
- balance: analysis.balance.toString(),
6084
- transactionCount: analysis.transactionCount,
6085
- riskLevel: analysis.riskLevel,
6086
- patterns: analysis.patterns
6087
- };
6088
- }
6089
- case "check_rug_indicators": {
6090
- const address = String(params["address"] ?? "");
6091
- const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6092
- const adapter = getAdapter(chain);
6093
- await adapter.connect(void 0, getConfig().etherscanApiKey);
6094
- const indicators = await detectRugIndicators(address, adapter);
6095
- return {
6096
- isHoneypot: indicators.isHoneypot,
6097
- hasLiquidityLock: indicators.hasLiquidityLock,
6098
- ownerCanMint: indicators.ownerCanMint,
6099
- ownerCanPause: indicators.ownerCanPause,
6100
- hasBlacklist: indicators.hasBlacklist,
6101
- highSellTax: indicators.highSellTax,
6102
- riskScore: indicators.riskScore,
6103
- details: indicators.details
6104
- };
6105
- }
6106
- case "get_market_data": {
6107
- const symbol = String(params["symbol"] ?? "");
6108
- try {
6109
- const binance = await fetchTickerPrice(symbol);
6110
- const gecko = await fetchMarketData(symbol).catch(() => null);
6111
- return {
6112
- symbol: binance.symbol,
6113
- name: gecko?.name ?? binance.symbol,
6114
- price: binance.price,
6115
- priceChange24h: binance.change24h,
6116
- priceChange7d: gecko?.priceChange7d ?? null,
6117
- volume24h: gecko?.volume24h ?? null,
6118
- marketCap: gecko?.marketCap ?? null,
6119
- rank: gecko?.rank ?? null,
6120
- source: "binance+coingecko"
6121
- };
6122
- } catch {
6123
- const data = await fetchMarketData(symbol);
6124
- if (!data) {
6125
- return { error: `No market data found for "${symbol}"` };
6126
- }
6127
- return data;
6185
+ // src/data/sqlite-store.ts
6186
+ function ensureAgentTables2() {
6187
+ const db2 = getDb();
6188
+ db2.exec(`
6189
+ CREATE TABLE IF NOT EXISTS agents (
6190
+ id TEXT PRIMARY KEY,
6191
+ name TEXT NOT NULL UNIQUE,
6192
+ strategy TEXT NOT NULL,
6193
+ pairs TEXT NOT NULL,
6194
+ interval_seconds INTEGER NOT NULL DEFAULT 60,
6195
+ created_at INTEGER NOT NULL,
6196
+ updated_at INTEGER NOT NULL
6197
+ )
6198
+ `);
6199
+ db2.exec(`
6200
+ CREATE TABLE IF NOT EXISTS agent_decisions (
6201
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6202
+ agent_id TEXT NOT NULL,
6203
+ symbol TEXT NOT NULL,
6204
+ action TEXT NOT NULL,
6205
+ confidence INTEGER NOT NULL,
6206
+ reasoning TEXT NOT NULL,
6207
+ signals TEXT NOT NULL,
6208
+ created_at INTEGER NOT NULL,
6209
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
6210
+ )
6211
+ `);
6212
+ }
6213
+ function rowToConfig(row) {
6214
+ return {
6215
+ id: row.id,
6216
+ name: row.name,
6217
+ strategy: row.strategy,
6218
+ pairs: JSON.parse(row.pairs),
6219
+ interval: row.interval_seconds,
6220
+ createdAt: row.created_at,
6221
+ updatedAt: row.updated_at
6222
+ };
6223
+ }
6224
+ var SqliteStore;
6225
+ var init_sqlite_store = __esm({
6226
+ "src/data/sqlite-store.ts"() {
6227
+ "use strict";
6228
+ init_cache();
6229
+ SqliteStore = class {
6230
+ // ---- Cache ---------------------------------------------------------------
6231
+ async getCached(key) {
6232
+ return getCached(key);
6128
6233
  }
6129
- }
6130
- case "search_upcoming_icos": {
6131
- const category = params["category"] ? String(params["category"]) : void 0;
6132
- const chain = params["chain"] ? String(params["chain"]) : void 0;
6133
- const roundType = params["roundType"] ? String(params["roundType"]) : void 0;
6134
- const projects = category || chain || roundType ? await searchICOs(void 0, category, chain, roundType) : await fetchUpcomingICOs();
6135
- return {
6136
- projects: projects.map((p) => ({
6137
- name: p.name,
6138
- category: p.category,
6139
- chain: p.chain,
6140
- roundType: p.roundType,
6141
- raisedAmount: p.raisedAmount,
6142
- valuation: p.valuation,
6143
- investors: p.investors.slice(0, 5),
6144
- startDate: p.startDate,
6145
- description: p.description,
6146
- website: p.website
6147
- }))
6148
- };
6149
- }
6150
- case "get_funding_history": {
6151
- const fundingName = String(params["name"] ?? "");
6152
- const type = String(params["type"] ?? "project");
6153
- if (type === "investor") {
6154
- const portfolio = await getInvestorPortfolio(fundingName);
6155
- return {
6156
- investor: fundingName,
6157
- investments: portfolio.map((p) => ({
6158
- name: p.name,
6159
- round: p.roundType,
6160
- amount: p.raisedAmount,
6161
- chain: p.chain,
6162
- category: p.category,
6163
- date: p.startDate
6164
- }))
6165
- };
6234
+ async setCache(key, value, ttlSeconds) {
6235
+ setCache(key, value, ttlSeconds);
6166
6236
  }
6167
- const history = await getProjectFundingHistory(fundingName);
6168
- return {
6169
- project: history.name,
6170
- rounds: history.rounds.map((r) => ({
6171
- round: r.roundType,
6172
- amount: r.raisedAmount,
6173
- valuation: r.valuation,
6174
- investors: r.investors.slice(0, 5),
6175
- date: r.startDate,
6176
- previousRounds: r.previousRounds
6177
- }))
6178
- };
6179
- }
6180
- case "search_token_dex": {
6181
- const query = String(params["query"] ?? "");
6182
- const pairs = await fetchTokenFromDex(query);
6183
- return {
6184
- results: pairs.slice(0, 5).map((p) => ({
6185
- name: p.baseToken.name,
6186
- symbol: p.baseToken.symbol,
6187
- chain: p.chainId,
6188
- dex: p.dexId,
6189
- priceUsd: p.priceUsd,
6190
- volume24h: p.volume?.h24 ?? 0,
6191
- liquidity: p.liquidity?.usd ?? 0,
6192
- priceChange24h: p.priceChange?.h24 ?? 0,
6193
- marketCap: p.marketCap ?? p.fdv ?? null,
6194
- buys24h: p.txns?.h24?.buys ?? 0,
6195
- sells24h: p.txns?.h24?.sells ?? 0,
6196
- pairAddress: p.pairAddress,
6197
- url: p.url
6198
- }))
6199
- };
6200
- }
6201
- case "get_trending": {
6202
- const trending = await fetchTrendingTokens();
6203
- return {
6204
- trending: trending.slice(0, 10).map((t) => ({
6205
- name: t.name,
6206
- symbol: t.symbol,
6207
- chain: t.chain,
6208
- priceUsd: t.priceUsd,
6209
- priceChange24h: t.priceChange24h,
6210
- volume24h: t.volume24h,
6211
- marketCap: t.marketCap,
6212
- source: t.source,
6213
- url: t.url
6214
- }))
6215
- };
6216
- }
6217
- case "get_crypto_news": {
6218
- const symbol = params["symbol"] ? String(params["symbol"]) : void 0;
6219
- const news = await fetchCryptoNews(symbol, getConfig().cryptopanicApiKey);
6220
- return {
6221
- news: news.slice(0, 10).map((n) => ({
6222
- title: n.title,
6223
- sentiment: n.sentiment,
6224
- source: n.source.title,
6225
- publishedAt: n.publishedAt,
6226
- url: n.url
6227
- }))
6228
- };
6229
- }
6230
- case "get_raises": {
6231
- const raises = await fetchRecentRaises(30);
6232
- let filtered = raises;
6233
- if (params["category"]) {
6234
- const cat = String(params["category"]).toLowerCase();
6235
- filtered = filtered.filter(
6236
- (r) => r.category?.toLowerCase().includes(cat) || r.sector?.toLowerCase().includes(cat)
6237
+ // ---- Agents --------------------------------------------------------------
6238
+ async createAgent(config2) {
6239
+ ensureAgentTables2();
6240
+ getDb().prepare(
6241
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
6242
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
6243
+ ).run(
6244
+ config2.id,
6245
+ config2.name,
6246
+ config2.strategy,
6247
+ JSON.stringify(config2.pairs),
6248
+ config2.interval,
6249
+ config2.createdAt,
6250
+ config2.updatedAt
6237
6251
  );
6252
+ return config2;
6238
6253
  }
6239
- if (params["chain"]) {
6240
- const ch = String(params["chain"]).toLowerCase();
6241
- filtered = filtered.filter((r) => r.chains.some((c) => c.toLowerCase().includes(ch)));
6254
+ async listAgents() {
6255
+ ensureAgentTables2();
6256
+ const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
6257
+ return rows.map(rowToConfig);
6242
6258
  }
6243
- return {
6244
- raises: filtered.slice(0, 10).map((r) => ({
6245
- name: r.name,
6246
- round: r.round,
6247
- amount: r.amount,
6248
- chains: r.chains,
6249
- sector: r.sector,
6250
- category: r.category,
6251
- leadInvestors: r.leadInvestors,
6252
- date: new Date(r.date * 1e3).toISOString().split("T")[0]
6253
- }))
6254
- };
6255
- }
6256
- case "get_token_security": {
6257
- const address = String(params["address"] ?? "");
6258
- const chain = String(params["chain"] ?? "ethereum");
6259
- const security = await checkTokenSecurity(address, chain);
6260
- if (!security) {
6261
- return { error: `No security data for ${address} on ${chain}` };
6259
+ async getAgentById(id) {
6260
+ ensureAgentTables2();
6261
+ const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
6262
+ return row ? rowToConfig(row) : null;
6262
6263
  }
6263
- return {
6264
- contractAddress: security.contractAddress,
6265
- chain: security.chain,
6266
- riskLevel: security.riskLevel,
6267
- isHoneypot: security.isHoneypot,
6268
- isMintable: security.isMintable,
6269
- buyTax: security.buyTax,
6270
- sellTax: security.sellTax,
6271
- isOpenSource: security.isOpenSource,
6272
- isProxy: security.isProxy,
6273
- hiddenOwner: security.hiddenOwner,
6274
- cannotBuy: security.cannotBuy,
6275
- cannotSellAll: security.cannotSellAll,
6276
- isBlacklisted: security.isBlacklisted,
6277
- holderCount: security.holderCount,
6278
- lpHolderCount: security.lpHolderCount,
6279
- creatorPercent: security.creatorPercent,
6280
- ownerPercent: security.ownerPercent,
6281
- trustList: security.trustList
6282
- };
6283
- }
6284
- case "get_fear_greed": {
6285
- const data = await fetchFearGreedIndex(7);
6286
- return {
6287
- current: { value: data.current.value, classification: data.current.classification },
6288
- previous: data.previous ? { value: data.previous.value, classification: data.previous.classification } : null,
6289
- history: data.history.map((h) => ({
6290
- value: h.value,
6291
- classification: h.classification,
6292
- date: new Date(h.timestamp * 1e3).toISOString().split("T")[0]
6293
- }))
6294
- };
6295
- }
6296
- case "get_derivatives_data": {
6297
- const symbol = String(params["symbol"] ?? "BTC");
6298
- const [fundingResult, oiResult] = await Promise.allSettled([
6299
- fetchFundingRate(symbol),
6300
- fetchOpenInterest(symbol)
6301
- ]);
6302
- const result = { symbol: symbol.toUpperCase() };
6303
- if (fundingResult.status === "fulfilled") {
6304
- result["fundingRate"] = fundingResult.value.fundingRate;
6305
- result["fundingRatePct"] = `${(fundingResult.value.fundingRate * 100).toFixed(4)}%`;
6306
- result["markPrice"] = fundingResult.value.markPrice;
6264
+ async getAgentByName(name) {
6265
+ ensureAgentTables2();
6266
+ const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
6267
+ return row ? rowToConfig(row) : null;
6307
6268
  }
6308
- if (oiResult.status === "fulfilled") {
6309
- result["openInterest"] = oiResult.value.openInterest;
6310
- result["openInterestNotional"] = oiResult.value.notionalValue;
6269
+ async deleteAgent(id) {
6270
+ ensureAgentTables2();
6271
+ const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
6272
+ getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
6273
+ return result.changes > 0;
6311
6274
  }
6312
- return result;
6313
- }
6314
- case "get_technical_analysis": {
6315
- const symbol = String(params["symbol"] ?? "BTC");
6316
- const timeframe = String(params["timeframe"] ?? "4h");
6317
- const ta = await analyzeTechnicals(symbol, timeframe);
6318
- return {
6319
- symbol: ta.symbol,
6320
- timeframe: ta.timeframe,
6321
- composite: ta.composite,
6322
- signals: ta.signals.map((s) => ({
6323
- name: s.name,
6324
- signal: s.signal,
6325
- strength: s.strength,
6326
- description: s.description
6327
- })),
6328
- indicators: {
6329
- rsi: ta.indicators.rsi ? Math.round(ta.indicators.rsi * 100) / 100 : null,
6330
- macd: ta.indicators.macd,
6331
- bollingerBands: ta.indicators.bollingerBands,
6332
- ema12: ta.indicators.ema12,
6333
- ema26: ta.indicators.ema26,
6334
- atr: ta.indicators.atr
6335
- }
6336
- };
6337
- }
6338
- case "get_prediction": {
6339
- const symbol = String(params["symbol"] ?? "BTC");
6340
- const prediction = await generatePrediction(symbol);
6341
- return {
6342
- symbol: prediction.symbol,
6343
- direction: prediction.direction,
6344
- confidence: prediction.confidence,
6345
- composite: prediction.composite,
6346
- timeframe: prediction.timeframe,
6347
- signals: prediction.signals,
6348
- reasoning: prediction.reasoning,
6349
- disclaimer: prediction.disclaimer
6350
- };
6351
- }
6352
- case "create_agent": {
6353
- const agentName = String(params["name"] ?? "");
6354
- const strategy = String(params["strategy"] ?? "momentum");
6355
- const pairsRaw = String(params["pairs"] ?? "BTC,ETH");
6356
- const interval = params["interval"] ? Number(params["interval"]) : 60;
6357
- const agentPairs = pairsRaw.split(",").map((p) => p.trim().toUpperCase());
6358
- const agent = createAgent(agentName, strategy, agentPairs, interval);
6359
- return {
6360
- id: agent.id,
6361
- name: agent.name,
6362
- strategy: agent.strategy,
6363
- pairs: agent.pairs,
6364
- interval: agent.interval,
6365
- message: `Agent "${agent.name}" created. Use /agent start ${agent.name} to activate.`
6366
- };
6367
- }
6368
- case "list_agents": {
6369
- const agents = listAgents();
6370
- return {
6371
- agents: agents.map((a) => {
6372
- const status = getAgentStatus(a.id);
6373
- return {
6374
- name: a.name,
6375
- strategy: a.strategy,
6376
- pairs: a.pairs,
6377
- interval: a.interval,
6378
- status: status?.status ?? "idle",
6379
- cycleCount: status?.cycleCount ?? 0
6380
- };
6381
- })
6382
- };
6383
- }
6384
- case "get_agent_status": {
6385
- const agentName = String(params["name"] ?? "");
6386
- const agent = getAgentByName(agentName);
6387
- if (!agent) return { error: `Agent "${agentName}" not found` };
6388
- const state = getAgentStatus(agent.id);
6389
- if (!state) return { error: `Agent "${agentName}" not found` };
6390
- const decisions = getRecentDecisions(agent.id, 5);
6391
- return {
6392
- name: state.config.name,
6393
- status: state.status,
6394
- strategy: state.config.strategy,
6395
- pairs: state.config.pairs,
6396
- cycleCount: state.cycleCount,
6397
- error: state.error,
6398
- recentDecisions: decisions.map((d) => ({
6399
- symbol: d.symbol,
6400
- action: d.decision.action,
6401
- confidence: d.decision.confidence,
6402
- reasoning: d.decision.reasoning,
6403
- timestamp: new Date(d.timestamp).toISOString()
6404
- }))
6405
- };
6406
- }
6407
- default:
6408
- return { error: `Unknown tool: ${name}` };
6409
- }
6410
- }
6411
- var init_tool_handler = __esm({
6412
- "src/ai/tool-handler.ts"() {
6413
- "use strict";
6414
- init_registry();
6415
- init_loader();
6416
- init_constants();
6417
- init_wallet_analyzer();
6418
- init_rug_detector();
6419
- init_market();
6420
- init_ico_tracker();
6421
- init_cryptopanic();
6422
- init_defillama();
6423
- init_binance();
6424
- init_goplus();
6425
- init_fear_greed();
6426
- init_technical_analysis();
6427
- init_predictor();
6428
- init_agent();
6275
+ async logDecision(result) {
6276
+ ensureAgentTables2();
6277
+ getDb().prepare(
6278
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
6279
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
6280
+ ).run(
6281
+ result.agentId,
6282
+ result.symbol,
6283
+ result.decision.action,
6284
+ result.decision.confidence,
6285
+ JSON.stringify(result.decision.reasoning),
6286
+ JSON.stringify(result.signals),
6287
+ result.timestamp
6288
+ );
6289
+ }
6290
+ async getDecisions(agentId, limit) {
6291
+ ensureAgentTables2();
6292
+ const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
6293
+ return rows.map((r) => ({
6294
+ agentId: r.agent_id,
6295
+ symbol: r.symbol,
6296
+ timestamp: r.created_at,
6297
+ signals: JSON.parse(r.signals),
6298
+ decision: {
6299
+ action: r.action,
6300
+ confidence: r.confidence,
6301
+ reasoning: JSON.parse(r.reasoning)
6302
+ }
6303
+ }));
6304
+ }
6305
+ // ---- Time-series (no-op for SQLite) --------------------------------------
6306
+ async insertOHLCV(_records) {
6307
+ }
6308
+ async queryOHLCV(_symbol, _timeframe, _from, _to) {
6309
+ return [];
6310
+ }
6311
+ // ---- Predictions (no-op for SQLite) --------------------------------------
6312
+ async logPrediction(_prediction) {
6313
+ }
6314
+ async getPredictionAccuracy(_model, _days) {
6315
+ return {
6316
+ model: _model,
6317
+ totalPredictions: 0,
6318
+ correctPredictions: 0,
6319
+ accuracy: 0,
6320
+ byDirection: {
6321
+ up: { total: 0, correct: 0, accuracy: 0 },
6322
+ down: { total: 0, correct: 0, accuracy: 0 },
6323
+ sideways: { total: 0, correct: 0, accuracy: 0 }
6324
+ },
6325
+ period: `${_days}d`
6326
+ };
6327
+ }
6328
+ // ---- Lifecycle -----------------------------------------------------------
6329
+ async close() {
6330
+ }
6331
+ };
6429
6332
  }
6430
6333
  });
6431
6334
 
6432
- // src/ai/tools.ts
6433
- var VIZZOR_TOOLS;
6434
- var init_tools = __esm({
6435
- "src/ai/tools.ts"() {
6335
+ // src/data/postgres-store.ts
6336
+ var postgres_store_exports = {};
6337
+ __export(postgres_store_exports, {
6338
+ PostgresStore: () => PostgresStore
6339
+ });
6340
+ import pg from "pg";
6341
+ import { readFileSync as readFileSync2 } from "fs";
6342
+ import { resolve as resolve2, dirname } from "path";
6343
+ import { fileURLToPath } from "url";
6344
+ var __filename, __dirname, PostgresStore;
6345
+ var init_postgres_store = __esm({
6346
+ "src/data/postgres-store.ts"() {
6436
6347
  "use strict";
6437
- VIZZOR_TOOLS = [
6438
- {
6439
- name: "get_token_info",
6440
- description: "Get on-chain token information including name, symbol, decimals, total supply, and top holders for a given contract address and chain.",
6441
- input_schema: {
6442
- type: "object",
6443
- properties: {
6444
- address: {
6445
- type: "string",
6446
- description: "The token contract address (e.g. 0x...)."
6447
- },
6448
- chain: {
6449
- type: "string",
6450
- description: 'The blockchain to query (e.g. "ethereum", "bsc", "polygon", "arbitrum").'
6451
- }
6452
- },
6453
- required: ["address", "chain"]
6454
- }
6455
- },
6456
- {
6457
- name: "analyze_wallet",
6458
- description: "Analyze a wallet address for transaction patterns, token holdings, DeFi interactions, and behavioral signals such as accumulation or distribution phases.",
6459
- input_schema: {
6460
- type: "object",
6461
- properties: {
6462
- address: {
6463
- type: "string",
6464
- description: "The wallet address to analyze."
6465
- },
6466
- chain: {
6467
- type: "string",
6468
- description: 'The blockchain the wallet resides on (e.g. "ethereum", "bsc").'
6469
- },
6470
- depth: {
6471
- type: "number",
6472
- description: "How many recent transactions to inspect. Defaults to 100."
6473
- }
6474
- },
6475
- required: ["address", "chain"]
6476
- }
6477
- },
6478
- {
6479
- name: "check_rug_indicators",
6480
- description: "Check a token for common rug pull indicators including honeypot detection, liquidity locks, ownership status, hidden mints, and holder concentration.",
6481
- input_schema: {
6482
- type: "object",
6483
- properties: {
6484
- address: {
6485
- type: "string",
6486
- description: "The token contract address to check."
6487
- },
6488
- chain: {
6489
- type: "string",
6490
- description: "The blockchain the token is deployed on."
6491
- }
6492
- },
6493
- required: ["address", "chain"]
6494
- }
6495
- },
6496
- {
6497
- name: "get_market_data",
6498
- description: "Get LIVE current market data for a token including price, 24h volume, market cap, price change percentages, and circulating supply. Returns real-time data \u2014 never quote prices from training data.",
6499
- input_schema: {
6500
- type: "object",
6501
- properties: {
6502
- symbol: {
6503
- type: "string",
6504
- description: 'The token ticker symbol (e.g. "ETH", "BTC", "UNI").'
6505
- },
6506
- currency: {
6507
- type: "string",
6508
- description: 'The fiat currency for price quotes. Defaults to "usd".'
6509
- }
6510
- },
6511
- required: ["symbol"]
6512
- }
6513
- },
6514
- {
6515
- name: "search_upcoming_icos",
6516
- description: "Search for CURRENT upcoming ICOs, token launches, and fundraising rounds filtered by category, blockchain, or round type. Returns LIVE data from DeFiLlama raises and Pump.fun launches, updated daily. MUST call for any ICO/launch question \u2014 training data is stale.",
6517
- input_schema: {
6518
- type: "object",
6519
- properties: {
6520
- category: {
6521
- type: "string",
6522
- description: 'Filter by project category (e.g. "defi", "gaming", "infrastructure", "nft", "ai").'
6523
- },
6524
- chain: {
6525
- type: "string",
6526
- description: 'Filter by blockchain (e.g. "ethereum", "solana", "bsc").'
6527
- },
6528
- roundType: {
6529
- type: "string",
6530
- description: 'Filter by funding round type (e.g. "Seed", "Pre-Seed", "Series A", "Series B", "Token Launch").'
6531
- },
6532
- limit: {
6533
- type: "number",
6534
- description: "Maximum number of results to return. Defaults to 10."
6535
- }
6536
- },
6537
- required: []
6538
- }
6539
- },
6540
- {
6541
- name: "get_funding_history",
6542
- description: "Get complete funding history for a project by name. Returns all known fundraising rounds with amounts, investors, valuations, and dates. Also works for looking up an investor portfolio.",
6543
- input_schema: {
6544
- type: "object",
6545
- properties: {
6546
- name: {
6547
- type: "string",
6548
- description: "Project name or investor name to look up."
6549
- },
6550
- type: {
6551
- type: "string",
6552
- description: 'Type of lookup: "project" for project funding history, "investor" for investor portfolio. Defaults to "project".'
6553
- }
6554
- },
6555
- required: ["name"]
6556
- }
6557
- },
6558
- {
6559
- name: "search_token_dex",
6560
- description: "Search for any token on decentralized exchanges via DexScreener. Returns real-time price, volume, liquidity, buy/sell counts, pair info. Works for all tokens including meme coins and newly launched tokens.",
6561
- input_schema: {
6562
- type: "object",
6563
- properties: {
6564
- query: {
6565
- type: "string",
6566
- description: "Token name, symbol, or contract address to search for."
6567
- }
6568
- },
6569
- required: ["query"]
6570
- }
6571
- },
6572
- {
6573
- name: "get_trending",
6574
- description: "Get REAL-TIME trending and hot tokens from DexScreener (boosted tokens) and CoinGecko trending combined. Returns what the market is excited about RIGHT NOW \u2014 trends change hourly. Always call for trending/hot token questions.",
6575
- input_schema: {
6576
- type: "object",
6577
- properties: {},
6578
- required: []
6579
- }
6580
- },
6581
- {
6582
- name: "get_crypto_news",
6583
- description: "Get LIVE latest crypto news with sentiment analysis for a specific token or the market in general. Returns current headlines from CryptoPanic \u2014 MUST call for news questions, training data is outdated.",
6584
- input_schema: {
6585
- type: "object",
6586
- properties: {
6587
- symbol: {
6588
- type: "string",
6589
- description: 'Token symbol to filter news for (e.g. "BTC", "ETH", "SOL"). Omit for general crypto news.'
6590
- }
6591
- },
6592
- required: []
6593
- }
6594
- },
6348
+ __filename = fileURLToPath(import.meta.url);
6349
+ __dirname = dirname(__filename);
6350
+ PostgresStore = class {
6351
+ pool;
6352
+ initialized = false;
6353
+ constructor(connectionUrl) {
6354
+ this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
6355
+ }
6356
+ async init() {
6357
+ if (this.initialized) return;
6358
+ const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
6359
+ const sql = readFileSync2(migrationPath, "utf-8");
6360
+ await this.pool.query(sql);
6361
+ this.initialized = true;
6362
+ }
6363
+ async query(text, params) {
6364
+ await this.init();
6365
+ return this.pool.query(text, params);
6366
+ }
6367
+ // ---- Cache ---------------------------------------------------------------
6368
+ async getCached(key) {
6369
+ const now = Math.floor(Date.now() / 1e3);
6370
+ const { rows } = await this.query(
6371
+ "SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
6372
+ [key, now]
6373
+ );
6374
+ if (rows.length === 0) return null;
6375
+ return rows[0].value;
6376
+ }
6377
+ async setCache(key, value, ttlSeconds) {
6378
+ const now = Math.floor(Date.now() / 1e3);
6379
+ const expiresAt = now + ttlSeconds;
6380
+ await this.query(
6381
+ `INSERT INTO cache (key, value, expires_at, created_at)
6382
+ VALUES ($1, $2, $3, $4)
6383
+ ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
6384
+ [key, JSON.stringify(value), expiresAt, now]
6385
+ );
6386
+ }
6387
+ // ---- Agents --------------------------------------------------------------
6388
+ async createAgent(config2) {
6389
+ await this.query(
6390
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
6391
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6392
+ [
6393
+ config2.id,
6394
+ config2.name,
6395
+ config2.strategy,
6396
+ JSON.stringify(config2.pairs),
6397
+ config2.interval,
6398
+ config2.createdAt,
6399
+ config2.updatedAt
6400
+ ]
6401
+ );
6402
+ return config2;
6403
+ }
6404
+ async listAgents() {
6405
+ const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
6406
+ return rows.map((r) => ({
6407
+ id: r.id,
6408
+ name: r.name,
6409
+ strategy: r.strategy,
6410
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6411
+ interval: r.interval_seconds,
6412
+ createdAt: Number(r.created_at),
6413
+ updatedAt: Number(r.updated_at)
6414
+ }));
6415
+ }
6416
+ async getAgentById(id) {
6417
+ const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
6418
+ if (rows.length === 0) return null;
6419
+ const r = rows[0];
6420
+ return {
6421
+ id: r.id,
6422
+ name: r.name,
6423
+ strategy: r.strategy,
6424
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6425
+ interval: r.interval_seconds,
6426
+ createdAt: Number(r.created_at),
6427
+ updatedAt: Number(r.updated_at)
6428
+ };
6429
+ }
6430
+ async getAgentByName(name) {
6431
+ const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
6432
+ if (rows.length === 0) return null;
6433
+ const r = rows[0];
6434
+ return {
6435
+ id: r.id,
6436
+ name: r.name,
6437
+ strategy: r.strategy,
6438
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6439
+ interval: r.interval_seconds,
6440
+ createdAt: Number(r.created_at),
6441
+ updatedAt: Number(r.updated_at)
6442
+ };
6443
+ }
6444
+ async deleteAgent(id) {
6445
+ const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
6446
+ return (result.rowCount ?? 0) > 0;
6447
+ }
6448
+ async logDecision(result) {
6449
+ await this.query(
6450
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
6451
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6452
+ [
6453
+ result.agentId,
6454
+ result.symbol,
6455
+ result.decision.action,
6456
+ result.decision.confidence,
6457
+ JSON.stringify(result.decision.reasoning),
6458
+ JSON.stringify(result.signals),
6459
+ result.timestamp
6460
+ ]
6461
+ );
6462
+ }
6463
+ async getDecisions(agentId, limit) {
6464
+ const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
6465
+ agentId,
6466
+ limit
6467
+ ]);
6468
+ return rows.map((r) => ({
6469
+ agentId: r.agent_id,
6470
+ symbol: r.symbol,
6471
+ timestamp: Number(r.created_at),
6472
+ signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
6473
+ decision: {
6474
+ action: r.action,
6475
+ confidence: r.confidence,
6476
+ reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
6477
+ }
6478
+ }));
6479
+ }
6480
+ // ---- Time-series ---------------------------------------------------------
6481
+ async insertOHLCV(records) {
6482
+ if (records.length === 0) return;
6483
+ const values = [];
6484
+ const placeholders = [];
6485
+ for (let i = 0; i < records.length; i++) {
6486
+ const r = records[i];
6487
+ const offset = i * 9;
6488
+ placeholders.push(
6489
+ `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
6490
+ );
6491
+ values.push(
6492
+ new Date(r.time).toISOString(),
6493
+ r.symbol,
6494
+ r.timeframe,
6495
+ r.open,
6496
+ r.high,
6497
+ r.low,
6498
+ r.close,
6499
+ r.volume,
6500
+ r.trades
6501
+ );
6502
+ }
6503
+ await this.query(
6504
+ `INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
6505
+ VALUES ${placeholders.join(", ")}
6506
+ ON CONFLICT (symbol, timeframe, time) DO UPDATE
6507
+ SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
6508
+ close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
6509
+ values
6510
+ );
6511
+ }
6512
+ async queryOHLCV(symbol, timeframe, from, to) {
6513
+ const { rows } = await this.query(
6514
+ `SELECT * FROM ohlcv
6515
+ WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
6516
+ ORDER BY time ASC`,
6517
+ [symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
6518
+ );
6519
+ return rows.map((r) => ({
6520
+ time: new Date(r.time).getTime(),
6521
+ symbol: r.symbol,
6522
+ timeframe: r.timeframe,
6523
+ open: r.open,
6524
+ high: r.high,
6525
+ low: r.low,
6526
+ close: r.close,
6527
+ volume: r.volume,
6528
+ trades: r.trades
6529
+ }));
6530
+ }
6531
+ // ---- Predictions ---------------------------------------------------------
6532
+ async logPrediction(prediction) {
6533
+ await this.query(
6534
+ `INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
6535
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6536
+ [
6537
+ prediction.symbol,
6538
+ prediction.model,
6539
+ prediction.direction,
6540
+ prediction.probability,
6541
+ prediction.horizon,
6542
+ JSON.stringify(prediction.features),
6543
+ new Date(prediction.predictedAt).toISOString()
6544
+ ]
6545
+ );
6546
+ }
6547
+ async getPredictionAccuracy(model, days) {
6548
+ const since = new Date(Date.now() - days * 864e5).toISOString();
6549
+ const { rows: totals } = await this.query(
6550
+ `SELECT direction,
6551
+ COUNT(*)::text AS total,
6552
+ COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
6553
+ FROM predictions
6554
+ WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
6555
+ GROUP BY direction`,
6556
+ [model, since]
6557
+ );
6558
+ const byDir = {
6559
+ up: { total: 0, correct: 0, accuracy: 0 },
6560
+ down: { total: 0, correct: 0, accuracy: 0 },
6561
+ sideways: { total: 0, correct: 0, accuracy: 0 }
6562
+ };
6563
+ let totalAll = 0;
6564
+ let correctAll = 0;
6565
+ for (const row of totals) {
6566
+ const t = parseInt(row.total, 10);
6567
+ const c = parseInt(row.correct, 10);
6568
+ totalAll += t;
6569
+ correctAll += c;
6570
+ const dir = row.direction;
6571
+ if (byDir[dir]) {
6572
+ byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
6573
+ }
6574
+ }
6575
+ return {
6576
+ model,
6577
+ totalPredictions: totalAll,
6578
+ correctPredictions: correctAll,
6579
+ accuracy: totalAll > 0 ? correctAll / totalAll : 0,
6580
+ byDirection: byDir,
6581
+ period: `${days}d`
6582
+ };
6583
+ }
6584
+ // ---- Lifecycle -----------------------------------------------------------
6585
+ async close() {
6586
+ await this.pool.end();
6587
+ }
6588
+ };
6589
+ }
6590
+ });
6591
+
6592
+ // src/data/store-factory.ts
6593
+ async function getStore(config2) {
6594
+ if (instance) return instance;
6595
+ if (config2.database?.type === "postgres" && config2.database.url) {
6596
+ const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
6597
+ instance = new PostgresStore2(config2.database.url);
6598
+ } else {
6599
+ instance = new SqliteStore();
6600
+ }
6601
+ return instance;
6602
+ }
6603
+ function getStoreInstance() {
6604
+ return instance;
6605
+ }
6606
+ var instance;
6607
+ var init_store_factory = __esm({
6608
+ "src/data/store-factory.ts"() {
6609
+ "use strict";
6610
+ init_sqlite_store();
6611
+ instance = null;
6612
+ }
6613
+ });
6614
+
6615
+ // src/ai/tool-handler.ts
6616
+ async function handleTool(name, input) {
6617
+ const params = input;
6618
+ switch (name) {
6619
+ case "get_token_info": {
6620
+ const address = String(params["address"] ?? "");
6621
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6622
+ const adapter = getAdapter(chain);
6623
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
6624
+ const info = await adapter.getTokenInfo(address);
6625
+ return {
6626
+ address: info.address,
6627
+ name: info.name,
6628
+ symbol: info.symbol,
6629
+ decimals: info.decimals,
6630
+ totalSupply: info.totalSupply.toString()
6631
+ };
6632
+ }
6633
+ case "analyze_wallet": {
6634
+ const address = String(params["address"] ?? "");
6635
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6636
+ const adapter = getAdapter(chain);
6637
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
6638
+ const analysis = await analyzeWallet(address, adapter);
6639
+ return {
6640
+ address: analysis.address,
6641
+ chain: analysis.chain,
6642
+ balance: analysis.balance.toString(),
6643
+ transactionCount: analysis.transactionCount,
6644
+ riskLevel: analysis.riskLevel,
6645
+ patterns: analysis.patterns
6646
+ };
6647
+ }
6648
+ case "check_rug_indicators": {
6649
+ const address = String(params["address"] ?? "");
6650
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6651
+ const adapter = getAdapter(chain);
6652
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
6653
+ const indicators = await detectRugIndicators(address, adapter);
6654
+ return {
6655
+ isHoneypot: indicators.isHoneypot,
6656
+ hasLiquidityLock: indicators.hasLiquidityLock,
6657
+ ownerCanMint: indicators.ownerCanMint,
6658
+ ownerCanPause: indicators.ownerCanPause,
6659
+ hasBlacklist: indicators.hasBlacklist,
6660
+ highSellTax: indicators.highSellTax,
6661
+ riskScore: indicators.riskScore,
6662
+ details: indicators.details
6663
+ };
6664
+ }
6665
+ case "get_market_data": {
6666
+ const symbol = String(params["symbol"] ?? "");
6667
+ try {
6668
+ const binance = await fetchTickerPrice(symbol);
6669
+ const gecko = await fetchMarketData(symbol).catch(() => null);
6670
+ return {
6671
+ symbol: binance.symbol,
6672
+ name: gecko?.name ?? binance.symbol,
6673
+ price: binance.price,
6674
+ priceChange24h: binance.change24h,
6675
+ priceChange7d: gecko?.priceChange7d ?? null,
6676
+ volume24h: gecko?.volume24h ?? null,
6677
+ marketCap: gecko?.marketCap ?? null,
6678
+ rank: gecko?.rank ?? null,
6679
+ source: "binance+coingecko"
6680
+ };
6681
+ } catch {
6682
+ const data = await fetchMarketData(symbol);
6683
+ if (!data) {
6684
+ return { error: `No market data found for "${symbol}"` };
6685
+ }
6686
+ return data;
6687
+ }
6688
+ }
6689
+ case "search_upcoming_icos": {
6690
+ const category = params["category"] ? String(params["category"]) : void 0;
6691
+ const chain = params["chain"] ? String(params["chain"]) : void 0;
6692
+ const roundType = params["roundType"] ? String(params["roundType"]) : void 0;
6693
+ const projects = category || chain || roundType ? await searchICOs(void 0, category, chain, roundType) : await fetchUpcomingICOs();
6694
+ return {
6695
+ projects: projects.map((p) => ({
6696
+ name: p.name,
6697
+ category: p.category,
6698
+ chain: p.chain,
6699
+ roundType: p.roundType,
6700
+ raisedAmount: p.raisedAmount,
6701
+ valuation: p.valuation,
6702
+ investors: p.investors.slice(0, 5),
6703
+ startDate: p.startDate,
6704
+ description: p.description,
6705
+ website: p.website
6706
+ }))
6707
+ };
6708
+ }
6709
+ case "get_funding_history": {
6710
+ const fundingName = String(params["name"] ?? "");
6711
+ const type = String(params["type"] ?? "project");
6712
+ if (type === "investor") {
6713
+ const portfolio = await getInvestorPortfolio(fundingName);
6714
+ return {
6715
+ investor: fundingName,
6716
+ investments: portfolio.map((p) => ({
6717
+ name: p.name,
6718
+ round: p.roundType,
6719
+ amount: p.raisedAmount,
6720
+ chain: p.chain,
6721
+ category: p.category,
6722
+ date: p.startDate
6723
+ }))
6724
+ };
6725
+ }
6726
+ const history = await getProjectFundingHistory(fundingName);
6727
+ return {
6728
+ project: history.name,
6729
+ rounds: history.rounds.map((r) => ({
6730
+ round: r.roundType,
6731
+ amount: r.raisedAmount,
6732
+ valuation: r.valuation,
6733
+ investors: r.investors.slice(0, 5),
6734
+ date: r.startDate,
6735
+ previousRounds: r.previousRounds
6736
+ }))
6737
+ };
6738
+ }
6739
+ case "search_token_dex": {
6740
+ const query = String(params["query"] ?? "");
6741
+ const pairs = await fetchTokenFromDex(query);
6742
+ return {
6743
+ results: pairs.slice(0, 5).map((p) => ({
6744
+ name: p.baseToken.name,
6745
+ symbol: p.baseToken.symbol,
6746
+ chain: p.chainId,
6747
+ dex: p.dexId,
6748
+ priceUsd: p.priceUsd,
6749
+ volume24h: p.volume?.h24 ?? 0,
6750
+ liquidity: p.liquidity?.usd ?? 0,
6751
+ priceChange24h: p.priceChange?.h24 ?? 0,
6752
+ marketCap: p.marketCap ?? p.fdv ?? null,
6753
+ buys24h: p.txns?.h24?.buys ?? 0,
6754
+ sells24h: p.txns?.h24?.sells ?? 0,
6755
+ pairAddress: p.pairAddress,
6756
+ url: p.url
6757
+ }))
6758
+ };
6759
+ }
6760
+ case "get_trending": {
6761
+ const trending = await fetchTrendingTokens();
6762
+ return {
6763
+ trending: trending.slice(0, 10).map((t) => ({
6764
+ name: t.name,
6765
+ symbol: t.symbol,
6766
+ chain: t.chain,
6767
+ priceUsd: t.priceUsd,
6768
+ priceChange24h: t.priceChange24h,
6769
+ volume24h: t.volume24h,
6770
+ marketCap: t.marketCap,
6771
+ source: t.source,
6772
+ url: t.url
6773
+ }))
6774
+ };
6775
+ }
6776
+ case "get_crypto_news": {
6777
+ const symbol = params["symbol"] ? String(params["symbol"]) : void 0;
6778
+ const news = await fetchCryptoNews(symbol, getConfig().cryptopanicApiKey);
6779
+ return {
6780
+ news: news.slice(0, 10).map((n) => ({
6781
+ title: n.title,
6782
+ sentiment: n.sentiment,
6783
+ source: n.source.title,
6784
+ publishedAt: n.publishedAt,
6785
+ url: n.url
6786
+ }))
6787
+ };
6788
+ }
6789
+ case "get_raises": {
6790
+ const raises = await fetchRecentRaises(30);
6791
+ let filtered = raises;
6792
+ if (params["category"]) {
6793
+ const cat = String(params["category"]).toLowerCase();
6794
+ filtered = filtered.filter(
6795
+ (r) => r.category?.toLowerCase().includes(cat) || r.sector?.toLowerCase().includes(cat)
6796
+ );
6797
+ }
6798
+ if (params["chain"]) {
6799
+ const ch = String(params["chain"]).toLowerCase();
6800
+ filtered = filtered.filter((r) => r.chains.some((c) => c.toLowerCase().includes(ch)));
6801
+ }
6802
+ return {
6803
+ raises: filtered.slice(0, 10).map((r) => ({
6804
+ name: r.name,
6805
+ round: r.round,
6806
+ amount: r.amount,
6807
+ chains: r.chains,
6808
+ sector: r.sector,
6809
+ category: r.category,
6810
+ leadInvestors: r.leadInvestors,
6811
+ date: new Date(r.date * 1e3).toISOString().split("T")[0]
6812
+ }))
6813
+ };
6814
+ }
6815
+ case "get_token_security": {
6816
+ const address = String(params["address"] ?? "");
6817
+ const chain = String(params["chain"] ?? "ethereum");
6818
+ const security = await checkTokenSecurity(address, chain);
6819
+ if (!security) {
6820
+ return { error: `No security data for ${address} on ${chain}` };
6821
+ }
6822
+ return {
6823
+ contractAddress: security.contractAddress,
6824
+ chain: security.chain,
6825
+ riskLevel: security.riskLevel,
6826
+ isHoneypot: security.isHoneypot,
6827
+ isMintable: security.isMintable,
6828
+ buyTax: security.buyTax,
6829
+ sellTax: security.sellTax,
6830
+ isOpenSource: security.isOpenSource,
6831
+ isProxy: security.isProxy,
6832
+ hiddenOwner: security.hiddenOwner,
6833
+ cannotBuy: security.cannotBuy,
6834
+ cannotSellAll: security.cannotSellAll,
6835
+ isBlacklisted: security.isBlacklisted,
6836
+ holderCount: security.holderCount,
6837
+ lpHolderCount: security.lpHolderCount,
6838
+ creatorPercent: security.creatorPercent,
6839
+ ownerPercent: security.ownerPercent,
6840
+ trustList: security.trustList
6841
+ };
6842
+ }
6843
+ case "get_fear_greed": {
6844
+ const data = await fetchFearGreedIndex(7);
6845
+ return {
6846
+ current: { value: data.current.value, classification: data.current.classification },
6847
+ previous: data.previous ? { value: data.previous.value, classification: data.previous.classification } : null,
6848
+ history: data.history.map((h) => ({
6849
+ value: h.value,
6850
+ classification: h.classification,
6851
+ date: new Date(h.timestamp * 1e3).toISOString().split("T")[0]
6852
+ }))
6853
+ };
6854
+ }
6855
+ case "get_derivatives_data": {
6856
+ const symbol = String(params["symbol"] ?? "BTC");
6857
+ const [fundingResult, oiResult] = await Promise.allSettled([
6858
+ fetchFundingRate(symbol),
6859
+ fetchOpenInterest(symbol)
6860
+ ]);
6861
+ const result = { symbol: symbol.toUpperCase() };
6862
+ if (fundingResult.status === "fulfilled") {
6863
+ result["fundingRate"] = fundingResult.value.fundingRate;
6864
+ result["fundingRatePct"] = `${(fundingResult.value.fundingRate * 100).toFixed(4)}%`;
6865
+ result["markPrice"] = fundingResult.value.markPrice;
6866
+ }
6867
+ if (oiResult.status === "fulfilled") {
6868
+ result["openInterest"] = oiResult.value.openInterest;
6869
+ result["openInterestNotional"] = oiResult.value.notionalValue;
6870
+ }
6871
+ return result;
6872
+ }
6873
+ case "get_technical_analysis": {
6874
+ const symbol = String(params["symbol"] ?? "BTC");
6875
+ const timeframe = String(params["timeframe"] ?? "4h");
6876
+ const ta = await analyzeTechnicals(symbol, timeframe);
6877
+ return {
6878
+ symbol: ta.symbol,
6879
+ timeframe: ta.timeframe,
6880
+ composite: ta.composite,
6881
+ signals: ta.signals.map((s) => ({
6882
+ name: s.name,
6883
+ signal: s.signal,
6884
+ strength: s.strength,
6885
+ description: s.description
6886
+ })),
6887
+ indicators: {
6888
+ rsi: ta.indicators.rsi ? Math.round(ta.indicators.rsi * 100) / 100 : null,
6889
+ macd: ta.indicators.macd,
6890
+ bollingerBands: ta.indicators.bollingerBands,
6891
+ ema12: ta.indicators.ema12,
6892
+ ema26: ta.indicators.ema26,
6893
+ atr: ta.indicators.atr
6894
+ }
6895
+ };
6896
+ }
6897
+ case "get_prediction": {
6898
+ const symbol = String(params["symbol"] ?? "BTC");
6899
+ const prediction = await generatePrediction(symbol);
6900
+ return {
6901
+ symbol: prediction.symbol,
6902
+ direction: prediction.direction,
6903
+ confidence: prediction.confidence,
6904
+ composite: prediction.composite,
6905
+ timeframe: prediction.timeframe,
6906
+ signals: prediction.signals,
6907
+ reasoning: prediction.reasoning,
6908
+ disclaimer: prediction.disclaimer
6909
+ };
6910
+ }
6911
+ case "get_ml_prediction": {
6912
+ const symbol = String(params["symbol"] ?? "BTC");
6913
+ const mlClient2 = getMLClient();
6914
+ if (!mlClient2) {
6915
+ const prediction = await generatePrediction(symbol);
6916
+ return {
6917
+ ...prediction,
6918
+ mlAvailable: false,
6919
+ note: "ML sidecar not configured; using rule-based prediction"
6920
+ };
6921
+ }
6922
+ const features = await buildFeatureVector(symbol);
6923
+ const mlPred = await mlClient2.predict(features);
6924
+ if (!mlPred) {
6925
+ const prediction = await generatePrediction(symbol);
6926
+ return {
6927
+ ...prediction,
6928
+ mlAvailable: false,
6929
+ note: "ML sidecar unavailable; using rule-based prediction"
6930
+ };
6931
+ }
6932
+ return {
6933
+ symbol: mlPred.symbol,
6934
+ direction: mlPred.direction,
6935
+ probability: mlPred.probability,
6936
+ confidence: mlPred.confidence,
6937
+ model: mlPred.model,
6938
+ horizon: mlPred.horizon,
6939
+ mlAvailable: true,
6940
+ features: {
6941
+ rsi: features.rsi,
6942
+ macdHistogram: features.macdHistogram,
6943
+ bollingerPercentB: features.bollingerPercentB,
6944
+ fundingRate: features.fundingRate,
6945
+ fearGreed: features.fearGreed,
6946
+ rsiSlope: features.rsiSlope,
6947
+ volumeRatio: features.volumeRatio,
6948
+ emaCrossoverPct: features.emaCrossoverPct,
6949
+ atrPct: features.atrPct
6950
+ }
6951
+ };
6952
+ }
6953
+ case "get_model_accuracy": {
6954
+ const model = String(params["model"] ?? "lstm-predictor");
6955
+ const days = params["days"] ? Number(params["days"]) : 30;
6956
+ const store = getStoreInstance();
6957
+ if (!store) {
6958
+ return { error: "DataStore not initialized. ML accuracy requires PostgreSQL backend." };
6959
+ }
6960
+ const accuracy = await store.getPredictionAccuracy(model, days);
6961
+ return {
6962
+ model: accuracy.model,
6963
+ period: accuracy.period,
6964
+ totalPredictions: accuracy.totalPredictions,
6965
+ correctPredictions: accuracy.correctPredictions,
6966
+ accuracy: `${(accuracy.accuracy * 100).toFixed(1)}%`,
6967
+ byDirection: {
6968
+ up: `${(accuracy.byDirection.up.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.up.correct}/${accuracy.byDirection.up.total})`,
6969
+ down: `${(accuracy.byDirection.down.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.down.correct}/${accuracy.byDirection.down.total})`,
6970
+ sideways: `${(accuracy.byDirection.sideways.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.sideways.correct}/${accuracy.byDirection.sideways.total})`
6971
+ }
6972
+ };
6973
+ }
6974
+ case "create_agent": {
6975
+ const agentName = String(params["name"] ?? "");
6976
+ const strategy = String(params["strategy"] ?? "momentum");
6977
+ const pairsRaw = String(params["pairs"] ?? "BTC,ETH");
6978
+ const interval = params["interval"] ? Number(params["interval"]) : 60;
6979
+ const agentPairs = pairsRaw.split(",").map((p) => p.trim().toUpperCase());
6980
+ const agent = createAgent(agentName, strategy, agentPairs, interval);
6981
+ return {
6982
+ id: agent.id,
6983
+ name: agent.name,
6984
+ strategy: agent.strategy,
6985
+ pairs: agent.pairs,
6986
+ interval: agent.interval,
6987
+ message: `Agent "${agent.name}" created. Use /agent start ${agent.name} to activate.`
6988
+ };
6989
+ }
6990
+ case "list_agents": {
6991
+ const agents = listAgents();
6992
+ return {
6993
+ agents: agents.map((a) => {
6994
+ const status = getAgentStatus(a.id);
6995
+ return {
6996
+ name: a.name,
6997
+ strategy: a.strategy,
6998
+ pairs: a.pairs,
6999
+ interval: a.interval,
7000
+ status: status?.status ?? "idle",
7001
+ cycleCount: status?.cycleCount ?? 0
7002
+ };
7003
+ })
7004
+ };
7005
+ }
7006
+ case "get_agent_status": {
7007
+ const agentName = String(params["name"] ?? "");
7008
+ const agent = getAgentByName(agentName);
7009
+ if (!agent) return { error: `Agent "${agentName}" not found` };
7010
+ const state = getAgentStatus(agent.id);
7011
+ if (!state) return { error: `Agent "${agentName}" not found` };
7012
+ const decisions = getRecentDecisions(agent.id, 5);
7013
+ return {
7014
+ name: state.config.name,
7015
+ status: state.status,
7016
+ strategy: state.config.strategy,
7017
+ pairs: state.config.pairs,
7018
+ cycleCount: state.cycleCount,
7019
+ error: state.error,
7020
+ recentDecisions: decisions.map((d) => ({
7021
+ symbol: d.symbol,
7022
+ action: d.decision.action,
7023
+ confidence: d.decision.confidence,
7024
+ reasoning: d.decision.reasoning,
7025
+ timestamp: new Date(d.timestamp).toISOString()
7026
+ }))
7027
+ };
7028
+ }
7029
+ default:
7030
+ return { error: `Unknown tool: ${name}` };
7031
+ }
7032
+ }
7033
+ var init_tool_handler = __esm({
7034
+ "src/ai/tool-handler.ts"() {
7035
+ "use strict";
7036
+ init_registry();
7037
+ init_loader();
7038
+ init_constants();
7039
+ init_wallet_analyzer();
7040
+ init_rug_detector();
7041
+ init_market();
7042
+ init_ico_tracker();
7043
+ init_cryptopanic();
7044
+ init_defillama();
7045
+ init_binance();
7046
+ init_goplus();
7047
+ init_fear_greed();
7048
+ init_technical_analysis();
7049
+ init_predictor();
7050
+ init_agent();
7051
+ init_client();
7052
+ init_feature_engineer();
7053
+ init_store_factory();
7054
+ }
7055
+ });
7056
+
7057
+ // src/ai/tools.ts
7058
+ var VIZZOR_TOOLS;
7059
+ var init_tools = __esm({
7060
+ "src/ai/tools.ts"() {
7061
+ "use strict";
7062
+ VIZZOR_TOOLS = [
7063
+ {
7064
+ name: "get_token_info",
7065
+ description: "Get on-chain token information including name, symbol, decimals, total supply, and top holders for a given contract address and chain.",
7066
+ input_schema: {
7067
+ type: "object",
7068
+ properties: {
7069
+ address: {
7070
+ type: "string",
7071
+ description: "The token contract address (e.g. 0x...)."
7072
+ },
7073
+ chain: {
7074
+ type: "string",
7075
+ description: 'The blockchain to query (e.g. "ethereum", "bsc", "polygon", "arbitrum").'
7076
+ }
7077
+ },
7078
+ required: ["address", "chain"]
7079
+ }
7080
+ },
7081
+ {
7082
+ name: "analyze_wallet",
7083
+ description: "Analyze a wallet address for transaction patterns, token holdings, DeFi interactions, and behavioral signals such as accumulation or distribution phases.",
7084
+ input_schema: {
7085
+ type: "object",
7086
+ properties: {
7087
+ address: {
7088
+ type: "string",
7089
+ description: "The wallet address to analyze."
7090
+ },
7091
+ chain: {
7092
+ type: "string",
7093
+ description: 'The blockchain the wallet resides on (e.g. "ethereum", "bsc").'
7094
+ },
7095
+ depth: {
7096
+ type: "number",
7097
+ description: "How many recent transactions to inspect. Defaults to 100."
7098
+ }
7099
+ },
7100
+ required: ["address", "chain"]
7101
+ }
7102
+ },
7103
+ {
7104
+ name: "check_rug_indicators",
7105
+ description: "Check a token for common rug pull indicators including honeypot detection, liquidity locks, ownership status, hidden mints, and holder concentration.",
7106
+ input_schema: {
7107
+ type: "object",
7108
+ properties: {
7109
+ address: {
7110
+ type: "string",
7111
+ description: "The token contract address to check."
7112
+ },
7113
+ chain: {
7114
+ type: "string",
7115
+ description: "The blockchain the token is deployed on."
7116
+ }
7117
+ },
7118
+ required: ["address", "chain"]
7119
+ }
7120
+ },
7121
+ {
7122
+ name: "get_market_data",
7123
+ description: "Get LIVE current market data for a token including price, 24h volume, market cap, price change percentages, and circulating supply. Returns real-time data \u2014 never quote prices from training data.",
7124
+ input_schema: {
7125
+ type: "object",
7126
+ properties: {
7127
+ symbol: {
7128
+ type: "string",
7129
+ description: 'The token ticker symbol (e.g. "ETH", "BTC", "UNI").'
7130
+ },
7131
+ currency: {
7132
+ type: "string",
7133
+ description: 'The fiat currency for price quotes. Defaults to "usd".'
7134
+ }
7135
+ },
7136
+ required: ["symbol"]
7137
+ }
7138
+ },
7139
+ {
7140
+ name: "search_upcoming_icos",
7141
+ description: "Search for CURRENT upcoming ICOs, token launches, and fundraising rounds filtered by category, blockchain, or round type. Returns LIVE data from DeFiLlama raises and Pump.fun launches, updated daily. MUST call for any ICO/launch question \u2014 training data is stale.",
7142
+ input_schema: {
7143
+ type: "object",
7144
+ properties: {
7145
+ category: {
7146
+ type: "string",
7147
+ description: 'Filter by project category (e.g. "defi", "gaming", "infrastructure", "nft", "ai").'
7148
+ },
7149
+ chain: {
7150
+ type: "string",
7151
+ description: 'Filter by blockchain (e.g. "ethereum", "solana", "bsc").'
7152
+ },
7153
+ roundType: {
7154
+ type: "string",
7155
+ description: 'Filter by funding round type (e.g. "Seed", "Pre-Seed", "Series A", "Series B", "Token Launch").'
7156
+ },
7157
+ limit: {
7158
+ type: "number",
7159
+ description: "Maximum number of results to return. Defaults to 10."
7160
+ }
7161
+ },
7162
+ required: []
7163
+ }
7164
+ },
7165
+ {
7166
+ name: "get_funding_history",
7167
+ description: "Get complete funding history for a project by name. Returns all known fundraising rounds with amounts, investors, valuations, and dates. Also works for looking up an investor portfolio.",
7168
+ input_schema: {
7169
+ type: "object",
7170
+ properties: {
7171
+ name: {
7172
+ type: "string",
7173
+ description: "Project name or investor name to look up."
7174
+ },
7175
+ type: {
7176
+ type: "string",
7177
+ description: 'Type of lookup: "project" for project funding history, "investor" for investor portfolio. Defaults to "project".'
7178
+ }
7179
+ },
7180
+ required: ["name"]
7181
+ }
7182
+ },
7183
+ {
7184
+ name: "search_token_dex",
7185
+ description: "Search for any token on decentralized exchanges via DexScreener. Returns real-time price, volume, liquidity, buy/sell counts, pair info. Works for all tokens including meme coins and newly launched tokens.",
7186
+ input_schema: {
7187
+ type: "object",
7188
+ properties: {
7189
+ query: {
7190
+ type: "string",
7191
+ description: "Token name, symbol, or contract address to search for."
7192
+ }
7193
+ },
7194
+ required: ["query"]
7195
+ }
7196
+ },
7197
+ {
7198
+ name: "get_trending",
7199
+ description: "Get REAL-TIME trending and hot tokens from DexScreener (boosted tokens) and CoinGecko trending combined. Returns what the market is excited about RIGHT NOW \u2014 trends change hourly. Always call for trending/hot token questions.",
7200
+ input_schema: {
7201
+ type: "object",
7202
+ properties: {},
7203
+ required: []
7204
+ }
7205
+ },
7206
+ {
7207
+ name: "get_crypto_news",
7208
+ description: "Get LIVE latest crypto news with sentiment analysis for a specific token or the market in general. Returns current headlines from CryptoPanic \u2014 MUST call for news questions, training data is outdated.",
7209
+ input_schema: {
7210
+ type: "object",
7211
+ properties: {
7212
+ symbol: {
7213
+ type: "string",
7214
+ description: 'Token symbol to filter news for (e.g. "BTC", "ETH", "SOL"). Omit for general crypto news.'
7215
+ }
7216
+ },
7217
+ required: []
7218
+ }
7219
+ },
6595
7220
  {
6596
7221
  name: "get_raises",
6597
7222
  description: "Get LIVE recent crypto fundraising rounds, venture capital investments, and token launches. Returns CURRENT data updated daily from DeFiLlama \u2014 always call this for ICO/funding questions, never use training data.",
@@ -6652,35 +7277,67 @@ var init_tools = __esm({
6652
7277
  }
6653
7278
  },
6654
7279
  {
6655
- name: "get_technical_analysis",
6656
- description: "Run technical analysis on a token: RSI, MACD, Bollinger Bands, EMA crossovers, ATR, OBV. Returns individual indicator signals and a composite direction with confidence.",
7280
+ name: "get_technical_analysis",
7281
+ description: "Run technical analysis on a token: RSI, MACD, Bollinger Bands, EMA crossovers, ATR, OBV. Returns individual indicator signals and a composite direction with confidence.",
7282
+ input_schema: {
7283
+ type: "object",
7284
+ properties: {
7285
+ symbol: {
7286
+ type: "string",
7287
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7288
+ },
7289
+ timeframe: {
7290
+ type: "string",
7291
+ description: 'Kline interval: "1h", "4h", "1d". Defaults to "4h".'
7292
+ }
7293
+ },
7294
+ required: ["symbol"]
7295
+ }
7296
+ },
7297
+ {
7298
+ name: "get_prediction",
7299
+ description: "Generate a multi-signal composite prediction combining technical analysis (40%), sentiment (20%), derivatives (20%), trend (15%), and macro (5%). Returns direction, confidence, composite score, and reasoning.",
7300
+ input_schema: {
7301
+ type: "object",
7302
+ properties: {
7303
+ symbol: {
7304
+ type: "string",
7305
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7306
+ }
7307
+ },
7308
+ required: ["symbol"]
7309
+ }
7310
+ },
7311
+ {
7312
+ name: "get_ml_prediction",
7313
+ description: "Get an ML-enhanced prediction using LSTM/Random Forest models from the ML sidecar. Returns direction, probability, model confidence, and feature importance. Falls back to rule-based prediction if ML sidecar is unavailable.",
6657
7314
  input_schema: {
6658
7315
  type: "object",
6659
7316
  properties: {
6660
7317
  symbol: {
6661
7318
  type: "string",
6662
7319
  description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
6663
- },
6664
- timeframe: {
6665
- type: "string",
6666
- description: 'Kline interval: "1h", "4h", "1d". Defaults to "4h".'
6667
7320
  }
6668
7321
  },
6669
7322
  required: ["symbol"]
6670
7323
  }
6671
7324
  },
6672
7325
  {
6673
- name: "get_prediction",
6674
- description: "Generate a multi-signal composite prediction combining technical analysis (40%), sentiment (20%), derivatives (20%), trend (15%), and macro (5%). Returns direction, confidence, composite score, and reasoning.",
7326
+ name: "get_model_accuracy",
7327
+ description: "Get historical accuracy metrics for ML prediction models. Shows total predictions, accuracy percentage, and breakdown by direction (up/down/sideways).",
6675
7328
  input_schema: {
6676
7329
  type: "object",
6677
7330
  properties: {
6678
- symbol: {
7331
+ model: {
6679
7332
  type: "string",
6680
- description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7333
+ description: 'Model name (e.g. "lstm-predictor", "signal-classifier"). Defaults to "lstm-predictor".'
7334
+ },
7335
+ days: {
7336
+ type: "number",
7337
+ description: "Number of days to look back for accuracy stats. Defaults to 30."
6681
7338
  }
6682
7339
  },
6683
- required: ["symbol"]
7340
+ required: []
6684
7341
  }
6685
7342
  },
6686
7343
  {
@@ -7176,1391 +7833,964 @@ Cycles: ${state?.cycleCount ?? 0} | Interval: ${agent.interval}s`,
7176
7833
  inline: false
7177
7834
  });
7178
7835
  }
7179
- await interaction.editReply({ embeds: [embed] });
7180
- }
7181
- async function handleAgentStartCommand(interaction) {
7182
- const name = interaction.options.getString("name", true);
7183
- const agent = getAgentByName(name);
7184
- if (!agent) {
7185
- await interaction.reply({
7186
- content: `Agent "${name}" not found. Use \`/agent_list\` to see your agents.`,
7187
- ephemeral: true
7188
- });
7189
- return;
7190
- }
7191
- const state = startAgent(agent.id);
7192
- await interaction.reply(
7193
- `\u{1F7E2} Agent "${state.config.name}" started. Monitoring ${state.config.pairs.join(", ")}.`
7194
- );
7195
- }
7196
- async function handleAgentStopCommand(interaction) {
7197
- const name = interaction.options.getString("name", true);
7198
- const agent = getAgentByName(name);
7199
- if (!agent) {
7200
- await interaction.reply({
7201
- content: `Agent "${name}" not found.`,
7202
- ephemeral: true
7203
- });
7204
- return;
7205
- }
7206
- const state = stopAgent(agent.id);
7207
- await interaction.reply(
7208
- `\u{1F534} Agent "${state.config.name}" stopped after ${state.cycleCount} cycles.`
7209
- );
7210
- }
7211
- async function handleAgentStatusCommand(interaction) {
7212
- await interaction.deferReply();
7213
- const name = interaction.options.getString("name", true);
7214
- const agent = getAgentByName(name);
7215
- if (!agent) {
7216
- await interaction.editReply(`Agent "${name}" not found.`);
7217
- return;
7218
- }
7219
- const state = getAgentStatus(agent.id);
7220
- if (!state) {
7221
- await interaction.editReply(`Agent "${name}" not found.`);
7222
- return;
7223
- }
7224
- const statusColor = state.status === "running" ? 65280 : state.status === "stopped" ? 16711680 : 8421504;
7225
- const statusEmoji = state.status === "running" ? "\u{1F7E2}" : state.status === "stopped" ? "\u{1F534}" : "\u26AA";
7226
- const embed = new EmbedBuilder().setTitle(`\u{1F916} Agent: ${state.config.name}`).setColor(statusColor).addFields(
7227
- { name: "Status", value: `${statusEmoji} ${state.status}`, inline: true },
7228
- { name: "Strategy", value: state.config.strategy, inline: true },
7229
- { name: "Pairs", value: state.config.pairs.join(", "), inline: true },
7230
- { name: "Interval", value: `${state.config.interval}s`, inline: true },
7231
- { name: "Cycles", value: String(state.cycleCount), inline: true }
7232
- ).setTimestamp();
7233
- if (state.error) {
7234
- embed.addFields({ name: "Error", value: state.error });
7235
- }
7236
- const decisions = getRecentDecisions(agent.id, 5);
7237
- if (decisions.length > 0) {
7238
- const decisionText = decisions.map((d) => {
7239
- const actionEmoji = d.decision.action === "buy" ? "\u{1F7E2}" : d.decision.action === "sell" ? "\u{1F534}" : "\u26AA";
7240
- const time = new Date(d.timestamp).toLocaleString();
7241
- return `${actionEmoji} ${d.symbol} ${d.decision.action.toUpperCase()} (${d.decision.confidence}%) \u2014 ${time}`;
7242
- }).join("\n");
7243
- embed.addFields({ name: "Recent Decisions", value: decisionText.slice(0, 1024) });
7244
- }
7245
- await interaction.editReply({ embeds: [embed] });
7246
- }
7247
- async function handleAgentDeleteCommand(interaction) {
7248
- const name = interaction.options.getString("name", true);
7249
- const agent = getAgentByName(name);
7250
- if (!agent) {
7251
- await interaction.reply({
7252
- content: `Agent "${name}" not found.`,
7253
- ephemeral: true
7254
- });
7255
- return;
7256
- }
7257
- deleteAgent(agent.id);
7258
- await interaction.reply(`\u{1F5D1} Agent "${name}" deleted.`);
7259
- }
7260
- var init_commands = __esm({
7261
- "src/discord/commands/index.ts"() {
7262
- "use strict";
7263
- init_registry();
7264
- init_loader();
7265
- init_project_analyzer();
7266
- init_risk_scorer();
7267
- init_wallet_analyzer();
7268
- init_contract_auditor();
7269
- init_market();
7270
- init_ico_tracker();
7271
- init_defillama();
7272
- init_binance();
7273
- init_predictor();
7274
- init_agent();
7275
- init_rate_limit();
7276
- }
7277
- });
7278
-
7279
- // src/discord/bot.ts
7280
- var bot_exports = {};
7281
- __export(bot_exports, {
7282
- startDiscordBot: () => startDiscordBot
7283
- });
7284
- import { Client, GatewayIntentBits, REST, Routes } from "discord.js";
7285
- async function startDiscordBot() {
7286
- const config2 = loadConfig();
7287
- const token = config2.discordToken;
7288
- if (!token) {
7289
- throw new Error("Discord token not configured. Run: vizzor config set discordToken <token>");
7290
- }
7291
- setConfig(config2);
7292
- setToolHandler(handleTool);
7293
- const client2 = new Client({
7294
- intents: [
7295
- GatewayIntentBits.Guilds,
7296
- GatewayIntentBits.GuildMessages,
7297
- GatewayIntentBits.MessageContent
7298
- ]
7299
- });
7300
- startRateLimitCleanup();
7301
- client2.once("ready", async (readyClient) => {
7302
- console.log(`Discord bot logged in as ${readyClient.user.tag}`);
7303
- const rest = new REST({ version: "10" }).setToken(token);
7304
- const commands = registerSlashCommands();
7305
- if (config2.discordGuildId) {
7306
- await rest.put(Routes.applicationGuildCommands(readyClient.user.id, config2.discordGuildId), {
7307
- body: commands
7308
- });
7309
- } else {
7310
- await rest.put(Routes.applicationCommands(readyClient.user.id), {
7311
- body: commands
7312
- });
7313
- }
7314
- console.log("Discord slash commands registered");
7315
- });
7316
- client2.on("interactionCreate", async (interaction) => {
7317
- if (!interaction.isChatInputCommand()) return;
7318
- await handleSlashCommand(interaction);
7319
- });
7320
- client2.on("messageCreate", async (message) => {
7321
- if (message.author.bot) return;
7322
- if (!client2.user || !message.mentions.has(client2.user)) return;
7323
- const text = message.content.replace(/<@!?\d+>/g, "").trim();
7324
- if (!text) {
7325
- await message.reply(
7326
- "Mention me with a question! e.g. `@Vizzor what is BTC price?`\nOr use slash commands: `/scan` `/trends` `/track` `/ico` `/audit` `/help`"
7327
- );
7328
- return;
7329
- }
7330
- await message.reply("\u{1F52E} Analyzing...");
7331
- try {
7332
- const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
7333
- const chunks = splitMessage(response, 1900);
7334
- for (const chunk of chunks) {
7335
- await message.reply(chunk);
7336
- }
7337
- } catch (error) {
7338
- const msg = error instanceof Error ? error.message : String(error);
7339
- await message.reply(`Analysis failed: ${msg}`);
7340
- }
7341
- });
7342
- await client2.login(token);
7343
- }
7344
- var init_bot = __esm({
7345
- "src/discord/bot.ts"() {
7346
- "use strict";
7347
- init_loader();
7348
- init_client();
7349
- init_tool_handler();
7350
- init_tools();
7351
- init_chat();
7352
- init_message_split();
7353
- init_commands();
7354
- init_rate_limit();
7355
- }
7356
- });
7357
-
7358
- // src/telegram/formatters/market.ts
7359
- function escapeMarkdown(text) {
7360
- return text.replace(/([_*[\]()~`>#+\-=|{}.!\\<>])/g, "\\$1");
7361
- }
7362
- function formatChange(change) {
7363
- const sign = change > 0 ? "\\+" : "";
7364
- const emoji = change > 0 ? "\u{1F7E2}" : change < 0 ? "\u{1F534}" : "\u26AA";
7365
- return `${emoji} ${sign}${escapeMarkdown(change.toFixed(2))}%`;
7366
- }
7367
- function formatVolume(volume) {
7368
- if (volume >= 1e9) return escapeMarkdown(`$${(volume / 1e9).toFixed(2)}B`);
7369
- if (volume >= 1e6) return escapeMarkdown(`$${(volume / 1e6).toFixed(2)}M`);
7370
- if (volume >= 1e3) return escapeMarkdown(`$${(volume / 1e3).toFixed(1)}K`);
7371
- return escapeMarkdown(`$${volume.toFixed(0)}`);
7372
- }
7373
- function formatTrending(items) {
7374
- const lines = ["*\u{1F525} Trending Tokens*", ""];
7375
- for (const t of items.slice(0, 10)) {
7376
- lines.push(
7377
- `\u2022 *${escapeMarkdown(t.symbol)}* \\(${escapeMarkdown(t.chain)}\\)`,
7378
- ` \u{1F4B2}${escapeMarkdown(t.priceUsd)} ${formatChange(t.priceChange24h)}`,
7379
- ` Vol: ${formatVolume(t.volume24h)} _\\[${escapeMarkdown(t.source)}\\]_`,
7380
- ""
7381
- );
7382
- }
7383
- return lines.join("\n");
7384
- }
7385
- function formatICOs(icos) {
7386
- const lines = ["*\u{1F680} Upcoming ICOs \\& Raises*", ""];
7387
- for (const ico of icos.slice(0, 10)) {
7388
- const amount = ico.amount ? escapeMarkdown(`$${(ico.amount / 1e6).toFixed(1)}M`) : "undisclosed";
7389
- const chains = ico.chains.length > 0 ? escapeMarkdown(ico.chains.join(", ")) : "multi\\-chain";
7390
- lines.push(
7391
- `\u2022 *${escapeMarkdown(ico.name)}* \u2014 ${escapeMarkdown(ico.round)} \\(${amount}\\)`,
7392
- ` ${chains} \\| ${escapeMarkdown(ico.date)}`
7393
- );
7394
- if (ico.leadInvestors.length > 0) {
7395
- lines.push(` Led by: ${escapeMarkdown(ico.leadInvestors.join(", "))}`);
7396
- }
7397
- lines.push("");
7398
- }
7399
- return lines.join("\n");
7400
- }
7401
- function formatAudit(contract, overallRisk, findings) {
7402
- const riskEmoji = overallRisk === "low" ? "\u{1F7E2}" : overallRisk === "medium" ? "\u{1F7E1}" : overallRisk === "high" ? "\u{1F7E0}" : "\u{1F534}";
7403
- const lines = [
7404
- `*\u{1F50D} Contract Audit*`,
7405
- `Address: \`${escapeMarkdown(contract)}\``,
7406
- `${riskEmoji} Risk: *${escapeMarkdown(overallRisk.toUpperCase())}*`,
7407
- ""
7408
- ];
7409
- if (findings.length > 0) {
7410
- lines.push("*Findings:*");
7411
- for (const f of findings) {
7412
- const sevEmoji = f.severity === "critical" ? "\u{1F534}" : f.severity === "high" ? "\u{1F7E0}" : f.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
7413
- lines.push(`${sevEmoji} *${escapeMarkdown(f.title)}*: ${escapeMarkdown(f.description)}`);
7414
- }
7415
- } else {
7416
- lines.push("\u2705 No significant findings\\.");
7417
- }
7418
- lines.push("", "_Not financial advice\\. DYOR\\._");
7419
- return lines.join("\n");
7836
+ await interaction.editReply({ embeds: [embed] });
7420
7837
  }
7421
- function formatWalletAnalysis(address, chain, balance, txCount, riskLevel, patterns) {
7422
- const riskEmoji = riskLevel === "low" ? "\u{1F7E2}" : riskLevel === "medium" ? "\u{1F7E1}" : riskLevel === "high" ? "\u{1F7E0}" : "\u{1F534}";
7423
- const lines = [
7424
- `*\u{1F45B} Wallet Analysis*`,
7425
- `Address: \`${escapeMarkdown(address)}\``,
7426
- `Chain: ${escapeMarkdown(chain)}`,
7427
- `Balance: ${escapeMarkdown(balance)} ETH`,
7428
- `Transactions: ${escapeMarkdown(String(txCount))}`,
7429
- `${riskEmoji} Risk: *${escapeMarkdown(riskLevel.toUpperCase())}*`,
7430
- ""
7431
- ];
7432
- if (patterns.length > 0) {
7433
- lines.push("*Patterns:*");
7434
- for (const p of patterns) {
7435
- lines.push(`\u2022 \\[${escapeMarkdown(p.severity)}\\] ${escapeMarkdown(p.description)}`);
7436
- }
7437
- } else {
7438
- lines.push("No unusual patterns detected\\.");
7838
+ async function handleAgentStartCommand(interaction) {
7839
+ const name = interaction.options.getString("name", true);
7840
+ const agent = getAgentByName(name);
7841
+ if (!agent) {
7842
+ await interaction.reply({
7843
+ content: `Agent "${name}" not found. Use \`/agent_list\` to see your agents.`,
7844
+ ephemeral: true
7845
+ });
7846
+ return;
7439
7847
  }
7440
- return lines.join("\n");
7848
+ const state = startAgent(agent.id);
7849
+ await interaction.reply(
7850
+ `\u{1F7E2} Agent "${state.config.name}" started. Monitoring ${state.config.pairs.join(", ")}.`
7851
+ );
7441
7852
  }
7442
- var init_market2 = __esm({
7443
- "src/telegram/formatters/market.ts"() {
7444
- "use strict";
7853
+ async function handleAgentStopCommand(interaction) {
7854
+ const name = interaction.options.getString("name", true);
7855
+ const agent = getAgentByName(name);
7856
+ if (!agent) {
7857
+ await interaction.reply({
7858
+ content: `Agent "${name}" not found.`,
7859
+ ephemeral: true
7860
+ });
7861
+ return;
7445
7862
  }
7446
- });
7447
-
7448
- // src/telegram/commands/index.ts
7449
- function registerCommands(bot) {
7450
- bot.command(
7451
- "start",
7452
- (ctx) => ctx.reply(
7453
- "*Welcome to Vizzor* \u2014 AI\\-powered crypto chronovisor\\.\n\n*Commands:*\n/scan \\<address\\> \u2014 Analyze a project\n/trends \u2014 Trending tokens \\+ market movers\n/track \\<wallet\\> \u2014 Wallet forensics\n/ico \u2014 Upcoming ICOs \\& raises\n/audit \\<contract\\> \u2014 Smart contract audit\n/price \\<symbol\\> \u2014 Quick price check\n/predict \\<symbol\\> \u2014 AI prediction\n/wallet \\<address\\> \u2014 Wallet balance\n/agent \u2014 Trading agent management\n/help \u2014 Show all commands\n\n_Send any message for AI\\-powered analysis_",
7454
- { parse_mode: "MarkdownV2" }
7455
- )
7456
- );
7457
- bot.command(
7458
- "help",
7459
- (ctx) => ctx.reply(
7460
- "*Vizzor Commands*\n\n*Analysis:*\n/scan \\<address\\> \u2014 Analyze token/project risk\n/track \\<wallet\\> \u2014 Wallet analysis \\& forensics\n/trends \u2014 Trending tokens from DexScreener\n/ico \u2014 Upcoming ICOs \\& fundraising rounds\n/audit \\<contract\\> \u2014 Smart contract audit\n\n*Quick Commands:*\n/price \\<symbol\\> \u2014 Live price check\n/predict \\<symbol\\> \u2014 AI prediction with signals\n/wallet \\<address\\> \u2014 ETH wallet balance\n\n*Agent Management:*\n/agent\\_create \\<name\\> \\<strategy\\> \\<pairs\\>\n/agent\\_list \u2014 List all agents\n/agent\\_start \\<name\\> \u2014 Start an agent\n/agent\\_stop \\<name\\> \u2014 Stop an agent\n/agent\\_status \\<name\\> \u2014 Agent status \\& decisions\n/agent\\_delete \\<name\\> \u2014 Delete an agent\n\n_Send any message for AI\\-powered analysis_",
7461
- { parse_mode: "MarkdownV2" }
7462
- )
7863
+ const state = stopAgent(agent.id);
7864
+ await interaction.reply(
7865
+ `\u{1F534} Agent "${state.config.name}" stopped after ${state.cycleCount} cycles.`
7463
7866
  );
7464
- bot.command("scan", handleScan2);
7465
- bot.command("trends", handleTrends2);
7466
- bot.command("track", handleTrack2);
7467
- bot.command("ico", handleIco);
7468
- bot.command("audit", handleAudit2);
7469
- bot.command("price", handlePrice);
7470
- bot.command("predict", handlePredict);
7471
- bot.command("wallet", handleWallet);
7472
- bot.command("agent", handleAgentHelp);
7473
- bot.command("agent_create", handleAgentCreate);
7474
- bot.command("agent_list", handleAgentList);
7475
- bot.command("agent_start", handleAgentStart);
7476
- bot.command("agent_stop", handleAgentStop);
7477
- bot.command("agent_status", handleAgentStatus);
7478
- bot.command("agent_delete", handleAgentDelete);
7479
7867
  }
7480
- async function handleScan2(ctx) {
7481
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7482
- const project = args2[0];
7483
- if (!project) {
7484
- await ctx.reply("Usage: /scan <project_address>", { parse_mode: void 0 });
7868
+ async function handleAgentStatusCommand(interaction) {
7869
+ await interaction.deferReply();
7870
+ const name = interaction.options.getString("name", true);
7871
+ const agent = getAgentByName(name);
7872
+ if (!agent) {
7873
+ await interaction.editReply(`Agent "${name}" not found.`);
7485
7874
  return;
7486
7875
  }
7487
- await ctx.reply("\u{1F50D} Scanning project...");
7488
- try {
7489
- const adapter = getAdapter("ethereum");
7490
- await adapter.connect(void 0, getConfig().etherscanApiKey);
7491
- const analysis = await analyzeProject(project, adapter);
7492
- const risk = assessRisk(analysis);
7493
- await adapter.disconnect();
7494
- const riskEmoji = risk.level === "low" ? "\u{1F7E2}" : risk.level === "medium" ? "\u{1F7E1}" : risk.level === "high" ? "\u{1F7E0}" : "\u{1F534}";
7495
- let message = `*Project Analysis*
7496
-
7497
- `;
7498
- message += `${riskEmoji} Risk: ${risk.score}/100 \\(${escapeMarkdown(risk.level.toUpperCase())}\\)
7499
- `;
7500
- message += `${escapeMarkdown(risk.summary)}
7501
- `;
7502
- if (analysis.token) {
7503
- message += `
7504
- Token: ${escapeMarkdown(analysis.token.name)} \\(${escapeMarkdown(analysis.token.symbol)}\\)`;
7505
- }
7506
- if (risk.factors.length > 0) {
7507
- message += "\n\n*Risk Factors*\n";
7508
- for (const factor of risk.factors) {
7509
- message += `\u2022 ${escapeMarkdown(factor)}
7510
- `;
7511
- }
7512
- }
7513
- message += "\n_Not financial advice\\. DYOR\\._";
7514
- await ctx.reply(message, { parse_mode: "MarkdownV2" });
7515
- } catch (error) {
7516
- const msg = error instanceof Error ? error.message : String(error);
7517
- await ctx.reply(`Error: ${msg}`);
7876
+ const state = getAgentStatus(agent.id);
7877
+ if (!state) {
7878
+ await interaction.editReply(`Agent "${name}" not found.`);
7879
+ return;
7518
7880
  }
7519
- }
7520
- async function handleTrends2(ctx) {
7521
- await ctx.reply("\u{1F4CA} Fetching trends...");
7522
- try {
7523
- const trending = await fetchTrendingTokens();
7524
- if (trending.length === 0) {
7525
- await ctx.reply("No trending data available right now.");
7526
- return;
7527
- }
7528
- const formatted = formatTrending(
7529
- trending.slice(0, 10).map((t) => ({
7530
- name: t.name,
7531
- symbol: t.symbol,
7532
- chain: t.chain,
7533
- priceUsd: t.priceUsd,
7534
- priceChange24h: t.priceChange24h,
7535
- volume24h: t.volume24h,
7536
- source: t.source
7537
- }))
7538
- );
7539
- await ctx.reply(formatted + "\n\n_Live data from DexScreener \\& CoinGecko_", {
7540
- parse_mode: "MarkdownV2"
7541
- });
7542
- } catch (error) {
7543
- const msg = error instanceof Error ? error.message : String(error);
7544
- await ctx.reply(`Failed to fetch trends: ${msg}`);
7881
+ const statusColor = state.status === "running" ? 65280 : state.status === "stopped" ? 16711680 : 8421504;
7882
+ const statusEmoji = state.status === "running" ? "\u{1F7E2}" : state.status === "stopped" ? "\u{1F534}" : "\u26AA";
7883
+ const embed = new EmbedBuilder().setTitle(`\u{1F916} Agent: ${state.config.name}`).setColor(statusColor).addFields(
7884
+ { name: "Status", value: `${statusEmoji} ${state.status}`, inline: true },
7885
+ { name: "Strategy", value: state.config.strategy, inline: true },
7886
+ { name: "Pairs", value: state.config.pairs.join(", "), inline: true },
7887
+ { name: "Interval", value: `${state.config.interval}s`, inline: true },
7888
+ { name: "Cycles", value: String(state.cycleCount), inline: true }
7889
+ ).setTimestamp();
7890
+ if (state.error) {
7891
+ embed.addFields({ name: "Error", value: state.error });
7892
+ }
7893
+ const decisions = getRecentDecisions(agent.id, 5);
7894
+ if (decisions.length > 0) {
7895
+ const decisionText = decisions.map((d) => {
7896
+ const actionEmoji = d.decision.action === "buy" ? "\u{1F7E2}" : d.decision.action === "sell" ? "\u{1F534}" : "\u26AA";
7897
+ const time = new Date(d.timestamp).toLocaleString();
7898
+ return `${actionEmoji} ${d.symbol} ${d.decision.action.toUpperCase()} (${d.decision.confidence}%) \u2014 ${time}`;
7899
+ }).join("\n");
7900
+ embed.addFields({ name: "Recent Decisions", value: decisionText.slice(0, 1024) });
7545
7901
  }
7902
+ await interaction.editReply({ embeds: [embed] });
7546
7903
  }
7547
- async function handleTrack2(ctx) {
7548
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7549
- const wallet = args2[0];
7550
- if (!wallet) {
7551
- await ctx.reply("Usage: /track <wallet_address>", { parse_mode: void 0 });
7904
+ async function handleAgentDeleteCommand(interaction) {
7905
+ const name = interaction.options.getString("name", true);
7906
+ const agent = getAgentByName(name);
7907
+ if (!agent) {
7908
+ await interaction.reply({
7909
+ content: `Agent "${name}" not found.`,
7910
+ ephemeral: true
7911
+ });
7552
7912
  return;
7553
7913
  }
7554
- await ctx.reply("\u{1F45B} Analyzing wallet...");
7555
- try {
7556
- const adapter = getAdapter("ethereum");
7557
- await adapter.connect(void 0, getConfig().etherscanApiKey);
7558
- const analysis = await analyzeWallet(wallet, adapter);
7559
- await adapter.disconnect();
7560
- const formatted = formatWalletAnalysis(
7561
- analysis.address,
7562
- analysis.chain,
7563
- analysis.balance.toString(),
7564
- analysis.transactionCount,
7565
- analysis.riskLevel,
7566
- analysis.patterns
7567
- );
7568
- await ctx.reply(formatted, { parse_mode: "MarkdownV2" });
7569
- } catch (error) {
7570
- const msg = error instanceof Error ? error.message : String(error);
7571
- await ctx.reply(`Wallet analysis failed: ${msg}`);
7572
- }
7914
+ deleteAgent(agent.id);
7915
+ await interaction.reply(`\u{1F5D1} Agent "${name}" deleted.`);
7573
7916
  }
7574
- async function handleIco(ctx) {
7575
- await ctx.reply("\u{1F680} Fetching ICOs and raises...");
7576
- try {
7577
- const [icosResult, raisesResult] = await Promise.allSettled([
7578
- fetchUpcomingICOs(),
7579
- fetchRecentRaises(30)
7580
- ]);
7581
- const icos = icosResult.status === "fulfilled" ? icosResult.value : [];
7582
- const raises = raisesResult.status === "fulfilled" ? raisesResult.value : [];
7583
- const items = [
7584
- ...raises.slice(0, 10).map((r) => ({
7585
- name: r.name,
7586
- round: r.round,
7587
- amount: r.amount,
7588
- chains: r.chains,
7589
- leadInvestors: r.leadInvestors,
7590
- date: new Date(r.date * 1e3).toISOString().split("T")[0] ?? ""
7591
- }))
7592
- ];
7593
- const raiseNames = new Set(items.map((i) => i.name.toLowerCase()));
7594
- for (const ico of icos.slice(0, 5)) {
7595
- if (!raiseNames.has(ico.name.toLowerCase())) {
7596
- items.push({
7597
- name: ico.name,
7598
- round: ico.status,
7599
- amount: null,
7600
- chains: [ico.chain ?? "multi-chain"],
7601
- leadInvestors: [],
7602
- date: ico.startDate ?? ""
7603
- });
7604
- }
7917
+ var init_commands = __esm({
7918
+ "src/discord/commands/index.ts"() {
7919
+ "use strict";
7920
+ init_registry();
7921
+ init_loader();
7922
+ init_project_analyzer();
7923
+ init_risk_scorer();
7924
+ init_wallet_analyzer();
7925
+ init_contract_auditor();
7926
+ init_market();
7927
+ init_ico_tracker();
7928
+ init_defillama();
7929
+ init_binance();
7930
+ init_predictor();
7931
+ init_agent();
7932
+ init_rate_limit();
7933
+ }
7934
+ });
7935
+
7936
+ // src/discord/bot.ts
7937
+ var bot_exports = {};
7938
+ __export(bot_exports, {
7939
+ startDiscordBot: () => startDiscordBot
7940
+ });
7941
+ import { Client, GatewayIntentBits, REST, Routes } from "discord.js";
7942
+ async function startDiscordBot() {
7943
+ const config2 = loadConfig();
7944
+ const token = config2.discordToken;
7945
+ if (!token) {
7946
+ throw new Error("Discord token not configured. Run: vizzor config set discordToken <token>");
7947
+ }
7948
+ setConfig(config2);
7949
+ setToolHandler(handleTool);
7950
+ const client2 = new Client({
7951
+ intents: [
7952
+ GatewayIntentBits.Guilds,
7953
+ GatewayIntentBits.GuildMessages,
7954
+ GatewayIntentBits.MessageContent
7955
+ ]
7956
+ });
7957
+ startRateLimitCleanup();
7958
+ client2.once("ready", async (readyClient) => {
7959
+ console.log(`Discord bot logged in as ${readyClient.user.tag}`);
7960
+ const rest = new REST({ version: "10" }).setToken(token);
7961
+ const commands = registerSlashCommands();
7962
+ if (config2.discordGuildId) {
7963
+ await rest.put(Routes.applicationGuildCommands(readyClient.user.id, config2.discordGuildId), {
7964
+ body: commands
7965
+ });
7966
+ } else {
7967
+ await rest.put(Routes.applicationCommands(readyClient.user.id), {
7968
+ body: commands
7969
+ });
7605
7970
  }
7606
- if (items.length === 0) {
7607
- await ctx.reply("No ICO or fundraising data available right now.");
7971
+ console.log("Discord slash commands registered");
7972
+ });
7973
+ client2.on("interactionCreate", async (interaction) => {
7974
+ if (!interaction.isChatInputCommand()) return;
7975
+ await handleSlashCommand(interaction);
7976
+ });
7977
+ client2.on("messageCreate", async (message) => {
7978
+ if (message.author.bot) return;
7979
+ if (!client2.user || !message.mentions.has(client2.user)) return;
7980
+ const text = message.content.replace(/<@!?\d+>/g, "").trim();
7981
+ if (!text) {
7982
+ await message.reply(
7983
+ "Mention me with a question! e.g. `@Vizzor what is BTC price?`\nOr use slash commands: `/scan` `/trends` `/track` `/ico` `/audit` `/help`"
7984
+ );
7608
7985
  return;
7609
7986
  }
7610
- const formatted = formatICOs(items);
7611
- await ctx.reply(formatted + "\n_Data from DeFiLlama \\& Pump\\.fun_", {
7612
- parse_mode: "MarkdownV2"
7613
- });
7614
- } catch (error) {
7615
- const msg = error instanceof Error ? error.message : String(error);
7616
- await ctx.reply(`Failed to fetch ICOs: ${msg}`);
7617
- }
7987
+ await message.reply("\u{1F52E} Analyzing...");
7988
+ try {
7989
+ const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
7990
+ const chunks = splitMessage(response, 1900);
7991
+ for (const chunk of chunks) {
7992
+ await message.reply(chunk);
7993
+ }
7994
+ } catch (error) {
7995
+ const msg = error instanceof Error ? error.message : String(error);
7996
+ await message.reply(`Analysis failed: ${msg}`);
7997
+ }
7998
+ });
7999
+ await client2.login(token);
7618
8000
  }
7619
- async function handleAudit2(ctx) {
7620
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7621
- const contract = args2[0];
7622
- if (!contract) {
7623
- await ctx.reply("Usage: /audit <contract_address>", { parse_mode: void 0 });
7624
- return;
8001
+ var init_bot = __esm({
8002
+ "src/discord/bot.ts"() {
8003
+ "use strict";
8004
+ init_loader();
8005
+ init_client2();
8006
+ init_tool_handler();
8007
+ init_tools();
8008
+ init_chat();
8009
+ init_message_split();
8010
+ init_commands();
8011
+ init_rate_limit();
7625
8012
  }
7626
- await ctx.reply("\u{1F50D} Auditing contract...");
7627
- try {
7628
- const adapter = getAdapter("ethereum");
7629
- await adapter.connect(void 0, getConfig().etherscanApiKey);
7630
- const result = await auditContract(contract, adapter);
7631
- await adapter.disconnect();
7632
- const formatted = formatAudit(
7633
- contract,
7634
- result.overallRisk,
7635
- result.findings.map((f) => ({
7636
- severity: f.severity,
7637
- title: f.title,
7638
- description: f.description
7639
- }))
8013
+ });
8014
+
8015
+ // src/telegram/formatters/market.ts
8016
+ function escapeMarkdown(text) {
8017
+ return text.replace(/([_*[\]()~`>#+\-=|{}.!\\<>])/g, "\\$1");
8018
+ }
8019
+ function formatChange(change) {
8020
+ const sign = change > 0 ? "\\+" : "";
8021
+ const emoji = change > 0 ? "\u{1F7E2}" : change < 0 ? "\u{1F534}" : "\u26AA";
8022
+ return `${emoji} ${sign}${escapeMarkdown(change.toFixed(2))}%`;
8023
+ }
8024
+ function formatVolume(volume) {
8025
+ if (volume >= 1e9) return escapeMarkdown(`$${(volume / 1e9).toFixed(2)}B`);
8026
+ if (volume >= 1e6) return escapeMarkdown(`$${(volume / 1e6).toFixed(2)}M`);
8027
+ if (volume >= 1e3) return escapeMarkdown(`$${(volume / 1e3).toFixed(1)}K`);
8028
+ return escapeMarkdown(`$${volume.toFixed(0)}`);
8029
+ }
8030
+ function formatTrending(items) {
8031
+ const lines = ["*\u{1F525} Trending Tokens*", ""];
8032
+ for (const t of items.slice(0, 10)) {
8033
+ lines.push(
8034
+ `\u2022 *${escapeMarkdown(t.symbol)}* \\(${escapeMarkdown(t.chain)}\\)`,
8035
+ ` \u{1F4B2}${escapeMarkdown(t.priceUsd)} ${formatChange(t.priceChange24h)}`,
8036
+ ` Vol: ${formatVolume(t.volume24h)} _\\[${escapeMarkdown(t.source)}\\]_`,
8037
+ ""
7640
8038
  );
7641
- await ctx.reply(formatted, { parse_mode: "MarkdownV2" });
7642
- } catch (error) {
7643
- const msg = error instanceof Error ? error.message : String(error);
7644
- await ctx.reply(`Audit failed: ${msg}`);
7645
8039
  }
8040
+ return lines.join("\n");
7646
8041
  }
7647
- async function handlePrice(ctx) {
7648
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7649
- const symbol = args2[0]?.toUpperCase();
7650
- if (!symbol) {
7651
- await ctx.reply("Usage: /price <symbol>\nExample: /price BTC");
7652
- return;
7653
- }
7654
- try {
7655
- const ticker = await fetchTickerPrice(symbol);
7656
- const changeEmoji = ticker.change24h >= 0 ? "\u{1F7E2}" : "\u{1F534}";
7657
- const changeSign = ticker.change24h >= 0 ? "+" : "";
7658
- await ctx.reply(
7659
- `\u{1F4B0} ${ticker.symbol}
7660
- Price: $${ticker.price.toLocaleString()}
7661
- ${changeEmoji} 24h: ${changeSign}${ticker.change24h.toFixed(2)}%
7662
-
7663
- Live from Binance`
8042
+ function formatICOs(icos) {
8043
+ const lines = ["*\u{1F680} Upcoming ICOs \\& Raises*", ""];
8044
+ for (const ico of icos.slice(0, 10)) {
8045
+ const amount = ico.amount ? escapeMarkdown(`$${(ico.amount / 1e6).toFixed(1)}M`) : "undisclosed";
8046
+ const chains = ico.chains.length > 0 ? escapeMarkdown(ico.chains.join(", ")) : "multi\\-chain";
8047
+ lines.push(
8048
+ `\u2022 *${escapeMarkdown(ico.name)}* \u2014 ${escapeMarkdown(ico.round)} \\(${amount}\\)`,
8049
+ ` ${chains} \\| ${escapeMarkdown(ico.date)}`
7664
8050
  );
7665
- } catch (error) {
7666
- const msg = error instanceof Error ? error.message : String(error);
7667
- await ctx.reply(`Price check failed: ${msg}`);
8051
+ if (ico.leadInvestors.length > 0) {
8052
+ lines.push(` Led by: ${escapeMarkdown(ico.leadInvestors.join(", "))}`);
8053
+ }
8054
+ lines.push("");
7668
8055
  }
8056
+ return lines.join("\n");
7669
8057
  }
7670
- async function handlePredict(ctx) {
7671
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7672
- const symbol = args2[0]?.toUpperCase();
7673
- if (!symbol) {
7674
- await ctx.reply("Usage: /predict <symbol>\nExample: /predict ETH");
7675
- return;
7676
- }
7677
- await ctx.reply(`\u{1F52E} Generating prediction for ${symbol}...`);
7678
- try {
7679
- const prediction = await generatePrediction(symbol);
7680
- const dirEmoji = prediction.direction === "up" ? "\u{1F7E2}" : prediction.direction === "down" ? "\u{1F534}" : "\u26AA";
7681
- let msg = `\u{1F52E} ${prediction.symbol} Prediction
7682
-
7683
- `;
7684
- msg += `${dirEmoji} Direction: ${prediction.direction.toUpperCase()}
7685
- `;
7686
- msg += `\u{1F4CA} Confidence: ${prediction.confidence}%
7687
- `;
7688
- msg += `\u{1F4C8} Composite: ${prediction.composite.toFixed(2)}
7689
- `;
7690
- msg += `\u23F1 Timeframe: ${prediction.timeframe}
7691
-
7692
- `;
7693
- msg += `Signals:
7694
- `;
7695
- msg += `\u2022 Technical: ${prediction.signals.technical}
7696
- `;
7697
- msg += `\u2022 Sentiment: ${prediction.signals.sentiment}
7698
- `;
7699
- msg += `\u2022 Derivatives: ${prediction.signals.derivatives}
7700
- `;
7701
- msg += `\u2022 Trend: ${prediction.signals.trend}
7702
- `;
7703
- msg += `\u2022 Macro: ${prediction.signals.macro}
7704
-
7705
- `;
7706
- if (prediction.reasoning.length > 0) {
7707
- msg += prediction.reasoning.join("\n") + "\n\n";
8058
+ function formatAudit(contract, overallRisk, findings) {
8059
+ const riskEmoji = overallRisk === "low" ? "\u{1F7E2}" : overallRisk === "medium" ? "\u{1F7E1}" : overallRisk === "high" ? "\u{1F7E0}" : "\u{1F534}";
8060
+ const lines = [
8061
+ `*\u{1F50D} Contract Audit*`,
8062
+ `Address: \`${escapeMarkdown(contract)}\``,
8063
+ `${riskEmoji} Risk: *${escapeMarkdown(overallRisk.toUpperCase())}*`,
8064
+ ""
8065
+ ];
8066
+ if (findings.length > 0) {
8067
+ lines.push("*Findings:*");
8068
+ for (const f of findings) {
8069
+ const sevEmoji = f.severity === "critical" ? "\u{1F534}" : f.severity === "high" ? "\u{1F7E0}" : f.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
8070
+ lines.push(`${sevEmoji} *${escapeMarkdown(f.title)}*: ${escapeMarkdown(f.description)}`);
7708
8071
  }
7709
- msg += prediction.disclaimer;
7710
- await ctx.reply(msg);
7711
- } catch (error) {
7712
- const msg = error instanceof Error ? error.message : String(error);
7713
- await ctx.reply(`Prediction failed: ${msg}`);
8072
+ } else {
8073
+ lines.push("\u2705 No significant findings\\.");
7714
8074
  }
8075
+ lines.push("", "_Not financial advice\\. DYOR\\._");
8076
+ return lines.join("\n");
7715
8077
  }
7716
- async function handleWallet(ctx) {
7717
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7718
- const address = args2[0];
7719
- if (!address) {
7720
- await ctx.reply("Usage: /wallet <ethereum_address>\nExample: /wallet 0x...");
7721
- return;
7722
- }
7723
- if (!isValidAddress(address)) {
7724
- await ctx.reply("Invalid Ethereum address. Must start with 0x followed by 40 hex characters.");
7725
- return;
7726
- }
7727
- try {
7728
- const balance = await getWalletBalance(address);
7729
- await ctx.reply(
7730
- `\u{1F45B} Wallet Balance
7731
-
7732
- Address: ${address}
7733
- Balance: ${balance} ETH
7734
-
7735
- Use /track <address> for full forensic analysis`
7736
- );
7737
- } catch (error) {
7738
- const msg = error instanceof Error ? error.message : String(error);
7739
- await ctx.reply(`Wallet query failed: ${msg}`);
8078
+ function formatWalletAnalysis(address, chain, balance, txCount, riskLevel, patterns) {
8079
+ const riskEmoji = riskLevel === "low" ? "\u{1F7E2}" : riskLevel === "medium" ? "\u{1F7E1}" : riskLevel === "high" ? "\u{1F7E0}" : "\u{1F534}";
8080
+ const lines = [
8081
+ `*\u{1F45B} Wallet Analysis*`,
8082
+ `Address: \`${escapeMarkdown(address)}\``,
8083
+ `Chain: ${escapeMarkdown(chain)}`,
8084
+ `Balance: ${escapeMarkdown(balance)} ETH`,
8085
+ `Transactions: ${escapeMarkdown(String(txCount))}`,
8086
+ `${riskEmoji} Risk: *${escapeMarkdown(riskLevel.toUpperCase())}*`,
8087
+ ""
8088
+ ];
8089
+ if (patterns.length > 0) {
8090
+ lines.push("*Patterns:*");
8091
+ for (const p of patterns) {
8092
+ lines.push(`\u2022 \\[${escapeMarkdown(p.severity)}\\] ${escapeMarkdown(p.description)}`);
8093
+ }
8094
+ } else {
8095
+ lines.push("No unusual patterns detected\\.");
7740
8096
  }
8097
+ return lines.join("\n");
7741
8098
  }
7742
- async function handleAgentHelp(ctx) {
7743
- const strategies = listStrategies();
7744
- await ctx.reply(
7745
- `\u{1F916} Agent Management
7746
-
7747
- Commands:
7748
- /agent_create <name> <strategy> <pairs> - Create agent
7749
- /agent_list - List all agents
7750
- /agent_start <name> - Start an agent
7751
- /agent_stop <name> - Stop an agent
7752
- /agent_status <name> - View status & decisions
7753
- /agent_delete <name> - Delete an agent
7754
-
7755
- Available strategies: ${strategies.join(", ")}
8099
+ var init_market2 = __esm({
8100
+ "src/telegram/formatters/market.ts"() {
8101
+ "use strict";
8102
+ }
8103
+ });
7756
8104
 
7757
- Example:
7758
- /agent_create mybot momentum BTC,ETH`
8105
+ // src/telegram/commands/index.ts
8106
+ function registerCommands(bot) {
8107
+ bot.command(
8108
+ "start",
8109
+ (ctx) => ctx.reply(
8110
+ "*Welcome to Vizzor* \u2014 AI\\-powered crypto chronovisor\\.\n\n*Commands:*\n/scan \\<address\\> \u2014 Analyze a project\n/trends \u2014 Trending tokens \\+ market movers\n/track \\<wallet\\> \u2014 Wallet forensics\n/ico \u2014 Upcoming ICOs \\& raises\n/audit \\<contract\\> \u2014 Smart contract audit\n/price \\<symbol\\> \u2014 Quick price check\n/predict \\<symbol\\> \u2014 AI prediction\n/wallet \\<address\\> \u2014 Wallet balance\n/agent \u2014 Trading agent management\n/help \u2014 Show all commands\n\n_Send any message for AI\\-powered analysis_",
8111
+ { parse_mode: "MarkdownV2" }
8112
+ )
8113
+ );
8114
+ bot.command(
8115
+ "help",
8116
+ (ctx) => ctx.reply(
8117
+ "*Vizzor Commands*\n\n*Analysis:*\n/scan \\<address\\> \u2014 Analyze token/project risk\n/track \\<wallet\\> \u2014 Wallet analysis \\& forensics\n/trends \u2014 Trending tokens from DexScreener\n/ico \u2014 Upcoming ICOs \\& fundraising rounds\n/audit \\<contract\\> \u2014 Smart contract audit\n\n*Quick Commands:*\n/price \\<symbol\\> \u2014 Live price check\n/predict \\<symbol\\> \u2014 AI prediction with signals\n/wallet \\<address\\> \u2014 ETH wallet balance\n\n*Agent Management:*\n/agent\\_create \\<name\\> \\<strategy\\> \\<pairs\\>\n/agent\\_list \u2014 List all agents\n/agent\\_start \\<name\\> \u2014 Start an agent\n/agent\\_stop \\<name\\> \u2014 Stop an agent\n/agent\\_status \\<name\\> \u2014 Agent status \\& decisions\n/agent\\_delete \\<name\\> \u2014 Delete an agent\n\n_Send any message for AI\\-powered analysis_",
8118
+ { parse_mode: "MarkdownV2" }
8119
+ )
7759
8120
  );
8121
+ bot.command("scan", handleScan2);
8122
+ bot.command("trends", handleTrends2);
8123
+ bot.command("track", handleTrack2);
8124
+ bot.command("ico", handleIco);
8125
+ bot.command("audit", handleAudit2);
8126
+ bot.command("price", handlePrice);
8127
+ bot.command("predict", handlePredict);
8128
+ bot.command("wallet", handleWallet);
8129
+ bot.command("agent", handleAgentHelp);
8130
+ bot.command("agent_create", handleAgentCreate);
8131
+ bot.command("agent_list", handleAgentList);
8132
+ bot.command("agent_start", handleAgentStart);
8133
+ bot.command("agent_stop", handleAgentStop);
8134
+ bot.command("agent_status", handleAgentStatus);
8135
+ bot.command("agent_delete", handleAgentDelete);
7760
8136
  }
7761
- async function handleAgentCreate(ctx) {
8137
+ async function handleScan2(ctx) {
7762
8138
  const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7763
- const name = args2[0];
7764
- const strategy = args2[1] ?? "momentum";
7765
- const pairsStr = args2[2] ?? "BTC,ETH";
7766
- if (!name) {
7767
- await ctx.reply(
7768
- "Usage: /agent_create <name> <strategy> <pairs>\nExample: /agent_create mybot momentum BTC,ETH,SOL"
7769
- );
8139
+ const project = args2[0];
8140
+ if (!project) {
8141
+ await ctx.reply("Usage: /scan <project_address>", { parse_mode: void 0 });
7770
8142
  return;
7771
8143
  }
8144
+ await ctx.reply("\u{1F50D} Scanning project...");
7772
8145
  try {
7773
- const pairs = pairsStr.split(",").map((p) => p.trim().toUpperCase());
7774
- const agent = createAgent(name, strategy, pairs);
7775
- await ctx.reply(
7776
- `\u2705 Agent Created
7777
-
7778
- Name: ${agent.name}
7779
- Strategy: ${agent.strategy}
7780
- Pairs: ${agent.pairs.join(", ")}
7781
- Interval: ${agent.interval}s
8146
+ const adapter = getAdapter("ethereum");
8147
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
8148
+ const analysis = await analyzeProject(project, adapter);
8149
+ const risk = assessRisk(analysis);
8150
+ await adapter.disconnect();
8151
+ const riskEmoji = risk.level === "low" ? "\u{1F7E2}" : risk.level === "medium" ? "\u{1F7E1}" : risk.level === "high" ? "\u{1F7E0}" : "\u{1F534}";
8152
+ let message = `*Project Analysis*
7782
8153
 
7783
- Use /agent_start ${agent.name} to activate`
7784
- );
7785
- } catch (error) {
7786
- const msg = error instanceof Error ? error.message : String(error);
7787
- await ctx.reply(`Failed to create agent: ${msg}`);
7788
- }
7789
- }
7790
- async function handleAgentList(ctx) {
7791
- const agents = listAgents();
7792
- if (agents.length === 0) {
7793
- await ctx.reply("No agents created yet. Use /agent_create to create one.");
7794
- return;
7795
- }
7796
- let msg = "\u{1F916} Your Agents\n\n";
7797
- for (const agent of agents) {
7798
- const state = getAgentStatus(agent.id);
7799
- const statusEmoji = state?.status === "running" ? "\u{1F7E2}" : state?.status === "stopped" ? "\u{1F534}" : "\u26AA";
7800
- msg += `${statusEmoji} ${agent.name} [${state?.status ?? "idle"}]
7801
8154
  `;
7802
- msg += ` Strategy: ${agent.strategy} | Pairs: ${agent.pairs.join(", ")}
8155
+ message += `${riskEmoji} Risk: ${risk.score}/100 \\(${escapeMarkdown(risk.level.toUpperCase())}\\)
7803
8156
  `;
7804
- msg += ` Cycles: ${state?.cycleCount ?? 0} | Interval: ${agent.interval}s
7805
-
8157
+ message += `${escapeMarkdown(risk.summary)}
8158
+ `;
8159
+ if (analysis.token) {
8160
+ message += `
8161
+ Token: ${escapeMarkdown(analysis.token.name)} \\(${escapeMarkdown(analysis.token.symbol)}\\)`;
8162
+ }
8163
+ if (risk.factors.length > 0) {
8164
+ message += "\n\n*Risk Factors*\n";
8165
+ for (const factor of risk.factors) {
8166
+ message += `\u2022 ${escapeMarkdown(factor)}
7806
8167
  `;
8168
+ }
8169
+ }
8170
+ message += "\n_Not financial advice\\. DYOR\\._";
8171
+ await ctx.reply(message, { parse_mode: "MarkdownV2" });
8172
+ } catch (error) {
8173
+ const msg = error instanceof Error ? error.message : String(error);
8174
+ await ctx.reply(`Error: ${msg}`);
7807
8175
  }
7808
- await ctx.reply(msg);
7809
8176
  }
7810
- async function handleAgentStart(ctx) {
7811
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7812
- const name = args2[0];
7813
- if (!name) {
7814
- await ctx.reply("Usage: /agent_start <name>");
7815
- return;
7816
- }
8177
+ async function handleTrends2(ctx) {
8178
+ await ctx.reply("\u{1F4CA} Fetching trends...");
7817
8179
  try {
7818
- const agent = getAgentByName(name);
7819
- if (!agent) {
7820
- await ctx.reply(`Agent "${name}" not found. Use /agent_list to see your agents.`);
8180
+ const trending = await fetchTrendingTokens();
8181
+ if (trending.length === 0) {
8182
+ await ctx.reply("No trending data available right now.");
7821
8183
  return;
7822
8184
  }
7823
- const state = startAgent(agent.id);
7824
- await ctx.reply(
7825
- `\u{1F7E2} Agent "${state.config.name}" started. Monitoring ${state.config.pairs.join(", ")}.`
8185
+ const formatted = formatTrending(
8186
+ trending.slice(0, 10).map((t) => ({
8187
+ name: t.name,
8188
+ symbol: t.symbol,
8189
+ chain: t.chain,
8190
+ priceUsd: t.priceUsd,
8191
+ priceChange24h: t.priceChange24h,
8192
+ volume24h: t.volume24h,
8193
+ source: t.source
8194
+ }))
7826
8195
  );
8196
+ await ctx.reply(formatted + "\n\n_Live data from DexScreener \\& CoinGecko_", {
8197
+ parse_mode: "MarkdownV2"
8198
+ });
7827
8199
  } catch (error) {
7828
8200
  const msg = error instanceof Error ? error.message : String(error);
7829
- await ctx.reply(`Failed to start agent: ${msg}`);
8201
+ await ctx.reply(`Failed to fetch trends: ${msg}`);
7830
8202
  }
7831
8203
  }
7832
- async function handleAgentStop(ctx) {
8204
+ async function handleTrack2(ctx) {
7833
8205
  const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7834
- const name = args2[0];
7835
- if (!name) {
7836
- await ctx.reply("Usage: /agent_stop <name>");
8206
+ const wallet = args2[0];
8207
+ if (!wallet) {
8208
+ await ctx.reply("Usage: /track <wallet_address>", { parse_mode: void 0 });
7837
8209
  return;
7838
8210
  }
8211
+ await ctx.reply("\u{1F45B} Analyzing wallet...");
7839
8212
  try {
7840
- const agent = getAgentByName(name);
7841
- if (!agent) {
7842
- await ctx.reply(`Agent "${name}" not found.`);
7843
- return;
7844
- }
7845
- const state = stopAgent(agent.id);
7846
- await ctx.reply(`\u{1F534} Agent "${state.config.name}" stopped after ${state.cycleCount} cycles.`);
8213
+ const adapter = getAdapter("ethereum");
8214
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
8215
+ const analysis = await analyzeWallet(wallet, adapter);
8216
+ await adapter.disconnect();
8217
+ const formatted = formatWalletAnalysis(
8218
+ analysis.address,
8219
+ analysis.chain,
8220
+ analysis.balance.toString(),
8221
+ analysis.transactionCount,
8222
+ analysis.riskLevel,
8223
+ analysis.patterns
8224
+ );
8225
+ await ctx.reply(formatted, { parse_mode: "MarkdownV2" });
7847
8226
  } catch (error) {
7848
8227
  const msg = error instanceof Error ? error.message : String(error);
7849
- await ctx.reply(`Failed to stop agent: ${msg}`);
8228
+ await ctx.reply(`Wallet analysis failed: ${msg}`);
7850
8229
  }
7851
8230
  }
7852
- async function handleAgentStatus(ctx) {
7853
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7854
- const name = args2[0];
7855
- if (!name) {
7856
- await ctx.reply("Usage: /agent_status <name>");
7857
- return;
7858
- }
7859
- const agent = getAgentByName(name);
7860
- if (!agent) {
7861
- await ctx.reply(`Agent "${name}" not found.`);
7862
- return;
7863
- }
7864
- const state = getAgentStatus(agent.id);
7865
- if (!state) {
7866
- await ctx.reply(`Agent "${name}" not found.`);
7867
- return;
7868
- }
7869
- const statusEmoji = state.status === "running" ? "\u{1F7E2}" : state.status === "stopped" ? "\u{1F534}" : "\u26AA";
7870
- let msg = `\u{1F916} Agent: ${state.config.name}
7871
-
7872
- `;
7873
- msg += `${statusEmoji} Status: ${state.status}
7874
- `;
7875
- msg += `Strategy: ${state.config.strategy}
7876
- `;
7877
- msg += `Pairs: ${state.config.pairs.join(", ")}
7878
- `;
7879
- msg += `Interval: ${state.config.interval}s
7880
- `;
7881
- msg += `Cycles: ${state.cycleCount}
7882
- `;
7883
- if (state.error) msg += `Error: ${state.error}
7884
- `;
7885
- const decisions = getRecentDecisions(agent.id, 5);
7886
- if (decisions.length > 0) {
7887
- msg += "\nRecent Decisions:\n";
7888
- for (const d of decisions) {
7889
- const actionEmoji = d.decision.action === "buy" ? "\u{1F7E2}" : d.decision.action === "sell" ? "\u{1F534}" : "\u26AA";
7890
- const time = new Date(d.timestamp).toLocaleString();
7891
- msg += `${actionEmoji} ${d.symbol} ${d.decision.action.toUpperCase()} (${d.decision.confidence}%) \u2014 ${time}
7892
- `;
7893
- if (d.decision.reasoning.length > 0) {
7894
- msg += ` \u2192 ${d.decision.reasoning[0]}
7895
- `;
8231
+ async function handleIco(ctx) {
8232
+ await ctx.reply("\u{1F680} Fetching ICOs and raises...");
8233
+ try {
8234
+ const [icosResult, raisesResult] = await Promise.allSettled([
8235
+ fetchUpcomingICOs(),
8236
+ fetchRecentRaises(30)
8237
+ ]);
8238
+ const icos = icosResult.status === "fulfilled" ? icosResult.value : [];
8239
+ const raises = raisesResult.status === "fulfilled" ? raisesResult.value : [];
8240
+ const items = [
8241
+ ...raises.slice(0, 10).map((r) => ({
8242
+ name: r.name,
8243
+ round: r.round,
8244
+ amount: r.amount,
8245
+ chains: r.chains,
8246
+ leadInvestors: r.leadInvestors,
8247
+ date: new Date(r.date * 1e3).toISOString().split("T")[0] ?? ""
8248
+ }))
8249
+ ];
8250
+ const raiseNames = new Set(items.map((i) => i.name.toLowerCase()));
8251
+ for (const ico of icos.slice(0, 5)) {
8252
+ if (!raiseNames.has(ico.name.toLowerCase())) {
8253
+ items.push({
8254
+ name: ico.name,
8255
+ round: ico.status,
8256
+ amount: null,
8257
+ chains: [ico.chain ?? "multi-chain"],
8258
+ leadInvestors: [],
8259
+ date: ico.startDate ?? ""
8260
+ });
7896
8261
  }
7897
8262
  }
8263
+ if (items.length === 0) {
8264
+ await ctx.reply("No ICO or fundraising data available right now.");
8265
+ return;
8266
+ }
8267
+ const formatted = formatICOs(items);
8268
+ await ctx.reply(formatted + "\n_Data from DeFiLlama \\& Pump\\.fun_", {
8269
+ parse_mode: "MarkdownV2"
8270
+ });
8271
+ } catch (error) {
8272
+ const msg = error instanceof Error ? error.message : String(error);
8273
+ await ctx.reply(`Failed to fetch ICOs: ${msg}`);
7898
8274
  }
7899
- await ctx.reply(msg);
7900
8275
  }
7901
- async function handleAgentDelete(ctx) {
8276
+ async function handleAudit2(ctx) {
7902
8277
  const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7903
- const name = args2[0];
7904
- if (!name) {
7905
- await ctx.reply("Usage: /agent_delete <name>");
8278
+ const contract = args2[0];
8279
+ if (!contract) {
8280
+ await ctx.reply("Usage: /audit <contract_address>", { parse_mode: void 0 });
7906
8281
  return;
7907
8282
  }
8283
+ await ctx.reply("\u{1F50D} Auditing contract...");
7908
8284
  try {
7909
- const agent = getAgentByName(name);
7910
- if (!agent) {
7911
- await ctx.reply(`Agent "${name}" not found.`);
7912
- return;
7913
- }
7914
- deleteAgent(agent.id);
7915
- await ctx.reply(`\u{1F5D1} Agent "${name}" deleted.`);
8285
+ const adapter = getAdapter("ethereum");
8286
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
8287
+ const result = await auditContract(contract, adapter);
8288
+ await adapter.disconnect();
8289
+ const formatted = formatAudit(
8290
+ contract,
8291
+ result.overallRisk,
8292
+ result.findings.map((f) => ({
8293
+ severity: f.severity,
8294
+ title: f.title,
8295
+ description: f.description
8296
+ }))
8297
+ );
8298
+ await ctx.reply(formatted, { parse_mode: "MarkdownV2" });
7916
8299
  } catch (error) {
7917
8300
  const msg = error instanceof Error ? error.message : String(error);
7918
- await ctx.reply(`Failed to delete agent: ${msg}`);
8301
+ await ctx.reply(`Audit failed: ${msg}`);
7919
8302
  }
7920
8303
  }
7921
- var init_commands2 = __esm({
7922
- "src/telegram/commands/index.ts"() {
7923
- "use strict";
7924
- init_registry();
7925
- init_loader();
7926
- init_project_analyzer();
7927
- init_risk_scorer();
7928
- init_wallet_analyzer();
7929
- init_contract_auditor();
7930
- init_market();
7931
- init_ico_tracker();
7932
- init_defillama();
7933
- init_binance();
7934
- init_predictor();
7935
- init_agent();
7936
- init_market2();
7937
- }
7938
- });
7939
-
7940
- // src/telegram/middleware/rate-limit.ts
7941
- async function rateLimitMiddleware(ctx, next) {
7942
- const userId = ctx.from?.id;
7943
- if (!userId) {
7944
- await next();
7945
- return;
7946
- }
7947
- const now = Date.now();
7948
- const entry = userLimits2.get(userId);
7949
- if (!entry || now >= entry.resetAt) {
7950
- userLimits2.set(userId, { count: 1, resetAt: now + WINDOW_MS2 });
7951
- await next();
7952
- return;
7953
- }
7954
- if (entry.count >= MAX_REQUESTS2) {
7955
- await ctx.reply("Rate limited. Please wait a moment before sending more commands.");
8304
+ async function handlePrice(ctx) {
8305
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8306
+ const symbol = args2[0]?.toUpperCase();
8307
+ if (!symbol) {
8308
+ await ctx.reply("Usage: /price <symbol>\nExample: /price BTC");
7956
8309
  return;
7957
8310
  }
7958
- entry.count++;
7959
- await next();
7960
- }
7961
- function startRateLimitCleanup2(intervalMs = 3e5) {
7962
- return setInterval(() => {
7963
- const now = Date.now();
7964
- for (const [userId, entry] of userLimits2) {
7965
- if (now >= entry.resetAt) {
7966
- userLimits2.delete(userId);
7967
- }
7968
- }
7969
- }, intervalMs);
7970
- }
7971
- var userLimits2, MAX_REQUESTS2, WINDOW_MS2;
7972
- var init_rate_limit2 = __esm({
7973
- "src/telegram/middleware/rate-limit.ts"() {
7974
- "use strict";
7975
- userLimits2 = /* @__PURE__ */ new Map();
7976
- MAX_REQUESTS2 = 10;
7977
- WINDOW_MS2 = 6e4;
7978
- }
7979
- });
7980
-
7981
- // src/telegram/bot.ts
7982
- var bot_exports2 = {};
7983
- __export(bot_exports2, {
7984
- startTelegramBot: () => startTelegramBot
7985
- });
7986
- import { Bot } from "grammy";
7987
- async function startTelegramBot() {
7988
- const config2 = loadConfig();
7989
- const token = config2.telegramToken;
7990
- if (!token) {
7991
- throw new Error("Telegram token not configured. Run: vizzor config set telegramToken <token>");
7992
- }
7993
- setConfig(config2);
7994
- setToolHandler(handleTool);
7995
- const bot = new Bot(token);
7996
- bot.use(rateLimitMiddleware);
7997
- registerCommands(bot);
7998
- bot.on("message:text", async (ctx) => {
7999
- const text = ctx.message.text;
8000
- if (text.startsWith("/")) return;
8001
- await ctx.reply("\u{1F52E} Analyzing...");
8002
- try {
8003
- const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
8004
- const chunks = splitMessage(response, 4e3);
8005
- for (const chunk of chunks) {
8006
- await ctx.reply(chunk);
8007
- }
8008
- } catch (error) {
8009
- const msg = error instanceof Error ? error.message : String(error);
8010
- await ctx.reply(`Analysis failed: ${msg}`);
8011
- }
8012
- });
8013
- bot.catch((err) => {
8014
- logger2.error({ err: err.message }, "Telegram bot error");
8015
- });
8016
- const cleanupInterval = startRateLimitCleanup2();
8017
- logger2.info("Telegram bot starting...");
8018
- await bot.start({
8019
- onStart: (botInfo) => {
8020
- logger2.info(`Telegram bot started as @${botInfo.username}`);
8021
- }
8022
- });
8023
- clearInterval(cleanupInterval);
8024
- }
8025
- var logger2;
8026
- var init_bot2 = __esm({
8027
- "src/telegram/bot.ts"() {
8028
- "use strict";
8029
- init_loader();
8030
- init_client();
8031
- init_tool_handler();
8032
- init_tools();
8033
- init_chat();
8034
- init_message_split();
8035
- init_commands2();
8036
- init_rate_limit2();
8037
- init_logger();
8038
- logger2 = createLogger("telegram-bot");
8039
- }
8040
- });
8311
+ try {
8312
+ const ticker = await fetchTickerPrice(symbol);
8313
+ const changeEmoji = ticker.change24h >= 0 ? "\u{1F7E2}" : "\u{1F534}";
8314
+ const changeSign = ticker.change24h >= 0 ? "+" : "";
8315
+ await ctx.reply(
8316
+ `\u{1F4B0} ${ticker.symbol}
8317
+ Price: $${ticker.price.toLocaleString()}
8318
+ ${changeEmoji} 24h: ${changeSign}${ticker.change24h.toFixed(2)}%
8041
8319
 
8042
- // src/cli/commands/bot.ts
8043
- var bot_exports3 = {};
8044
- __export(bot_exports3, {
8045
- handleBotStart: () => handleBotStart,
8046
- handleBotValidate: () => handleBotValidate
8047
- });
8048
- import chalk7 from "chalk";
8049
- async function handleBotStart(options) {
8050
- const config2 = getConfig();
8051
- const startDiscord = options.discord || options.all;
8052
- const startTelegram = options.telegram || options.all;
8053
- if (!startDiscord && !startTelegram) {
8054
- console.log(chalk7.yellow("Specify which bot to start:"));
8055
- console.log(" --discord Start Discord bot");
8056
- console.log(" --telegram Start Telegram bot");
8057
- console.log(" --all Start all bots");
8058
- return;
8059
- }
8060
- const promises = [];
8061
- if (startDiscord) {
8062
- requireKey("DISCORD_TOKEN", config2.discordToken);
8063
- console.log(chalk7.blue("Starting Discord bot..."));
8064
- const { startDiscordBot: startDiscordBot2 } = await Promise.resolve().then(() => (init_bot(), bot_exports));
8065
- promises.push(startDiscordBot2());
8066
- }
8067
- if (startTelegram) {
8068
- requireKey("TELEGRAM_BOT_TOKEN", config2.telegramToken);
8069
- console.log(chalk7.blue("Starting Telegram bot..."));
8070
- const { startTelegramBot: startTelegramBot2 } = await Promise.resolve().then(() => (init_bot2(), bot_exports2));
8071
- promises.push(startTelegramBot2());
8072
- }
8073
- const shutdown = () => {
8074
- console.log(chalk7.dim("\nShutting down bots..."));
8075
- process.exit(0);
8076
- };
8077
- process.on("SIGINT", shutdown);
8078
- process.on("SIGTERM", shutdown);
8079
- await Promise.all(promises);
8080
- }
8081
- function handleBotValidate() {
8082
- const config2 = getConfig();
8083
- console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
8084
- const checks = [
8085
- { label: "Anthropic API Key", isSet: hasKey(config2.anthropicApiKey), required: true },
8086
- { label: "Etherscan API Key", isSet: hasKey(config2.etherscanApiKey), required: true },
8087
- { label: "Discord Token", isSet: hasKey(config2.discordToken), required: false },
8088
- { label: "Discord Guild ID", isSet: hasKey(config2.discordGuildId), required: false },
8089
- { label: "Telegram Token", isSet: hasKey(config2.telegramToken), required: false },
8090
- { label: "CryptoPanic Key", isSet: hasKey(config2.cryptopanicApiKey), required: false }
8091
- ];
8092
- let allRequired = true;
8093
- for (const check of checks) {
8094
- const status = check.isSet ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
8095
- console.log(` ${status.padEnd(18)} ${check.label}`);
8096
- if (check.required && !check.isSet) allRequired = false;
8097
- }
8098
- console.log();
8099
- if (hasKey(config2.discordToken)) {
8100
- console.log(chalk7.green(" Discord bot: Ready to start"));
8101
- } else {
8102
- console.log(chalk7.yellow(" Discord bot: Set discordToken to enable"));
8103
- console.log(chalk7.dim(" vizzor config set discordToken <token>"));
8320
+ Live from Binance`
8321
+ );
8322
+ } catch (error) {
8323
+ const msg = error instanceof Error ? error.message : String(error);
8324
+ await ctx.reply(`Price check failed: ${msg}`);
8104
8325
  }
8105
- if (hasKey(config2.telegramToken)) {
8106
- console.log(chalk7.green(" Telegram bot: Ready to start"));
8107
- } else {
8108
- console.log(chalk7.yellow(" Telegram bot: Set telegramToken to enable"));
8109
- console.log(chalk7.dim(" vizzor config set telegramToken <token>"));
8326
+ }
8327
+ async function handlePredict(ctx) {
8328
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8329
+ const symbol = args2[0]?.toUpperCase();
8330
+ if (!symbol) {
8331
+ await ctx.reply("Usage: /predict <symbol>\nExample: /predict ETH");
8332
+ return;
8110
8333
  }
8111
- console.log();
8112
- if (!allRequired) {
8113
- console.log(chalk7.red("Required keys are missing. Run: vizzor config set <key> <value>"));
8114
- } else if (hasKey(config2.discordToken) && hasKey(config2.telegramToken)) {
8115
- console.log(chalk7.green("All bots ready. Run: vizzor bot start --all"));
8116
- } else if (hasKey(config2.discordToken) || hasKey(config2.telegramToken)) {
8117
- const which = hasKey(config2.discordToken) ? "--discord" : "--telegram";
8118
- console.log(chalk7.green(`Bot ready. Run: vizzor bot start ${which}`));
8334
+ await ctx.reply(`\u{1F52E} Generating prediction for ${symbol}...`);
8335
+ try {
8336
+ const prediction = await generatePrediction(symbol);
8337
+ const dirEmoji = prediction.direction === "up" ? "\u{1F7E2}" : prediction.direction === "down" ? "\u{1F534}" : "\u26AA";
8338
+ let msg = `\u{1F52E} ${prediction.symbol} Prediction
8339
+
8340
+ `;
8341
+ msg += `${dirEmoji} Direction: ${prediction.direction.toUpperCase()}
8342
+ `;
8343
+ msg += `\u{1F4CA} Confidence: ${prediction.confidence}%
8344
+ `;
8345
+ msg += `\u{1F4C8} Composite: ${prediction.composite.toFixed(2)}
8346
+ `;
8347
+ msg += `\u23F1 Timeframe: ${prediction.timeframe}
8348
+
8349
+ `;
8350
+ msg += `Signals:
8351
+ `;
8352
+ msg += `\u2022 Technical: ${prediction.signals.technical}
8353
+ `;
8354
+ msg += `\u2022 Sentiment: ${prediction.signals.sentiment}
8355
+ `;
8356
+ msg += `\u2022 Derivatives: ${prediction.signals.derivatives}
8357
+ `;
8358
+ msg += `\u2022 Trend: ${prediction.signals.trend}
8359
+ `;
8360
+ msg += `\u2022 Macro: ${prediction.signals.macro}
8361
+
8362
+ `;
8363
+ if (prediction.reasoning.length > 0) {
8364
+ msg += prediction.reasoning.join("\n") + "\n\n";
8365
+ }
8366
+ msg += prediction.disclaimer;
8367
+ await ctx.reply(msg);
8368
+ } catch (error) {
8369
+ const msg = error instanceof Error ? error.message : String(error);
8370
+ await ctx.reply(`Prediction failed: ${msg}`);
8119
8371
  }
8120
8372
  }
8121
- var init_bot3 = __esm({
8122
- "src/cli/commands/bot.ts"() {
8123
- "use strict";
8124
- init_loader();
8125
- init_keys();
8373
+ async function handleWallet(ctx) {
8374
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8375
+ const address = args2[0];
8376
+ if (!address) {
8377
+ await ctx.reply("Usage: /wallet <ethereum_address>\nExample: /wallet 0x...");
8378
+ return;
8126
8379
  }
8127
- });
8380
+ if (!isValidAddress(address)) {
8381
+ await ctx.reply("Invalid Ethereum address. Must start with 0x followed by 40 hex characters.");
8382
+ return;
8383
+ }
8384
+ try {
8385
+ const balance = await getWalletBalance(address);
8386
+ await ctx.reply(
8387
+ `\u{1F45B} Wallet Balance
8128
8388
 
8129
- // src/data/sqlite-store.ts
8130
- function ensureAgentTables2() {
8131
- const db2 = getDb();
8132
- db2.exec(`
8133
- CREATE TABLE IF NOT EXISTS agents (
8134
- id TEXT PRIMARY KEY,
8135
- name TEXT NOT NULL UNIQUE,
8136
- strategy TEXT NOT NULL,
8137
- pairs TEXT NOT NULL,
8138
- interval_seconds INTEGER NOT NULL DEFAULT 60,
8139
- created_at INTEGER NOT NULL,
8140
- updated_at INTEGER NOT NULL
8141
- )
8142
- `);
8143
- db2.exec(`
8144
- CREATE TABLE IF NOT EXISTS agent_decisions (
8145
- id INTEGER PRIMARY KEY AUTOINCREMENT,
8146
- agent_id TEXT NOT NULL,
8147
- symbol TEXT NOT NULL,
8148
- action TEXT NOT NULL,
8149
- confidence INTEGER NOT NULL,
8150
- reasoning TEXT NOT NULL,
8151
- signals TEXT NOT NULL,
8152
- created_at INTEGER NOT NULL,
8153
- FOREIGN KEY (agent_id) REFERENCES agents(id)
8154
- )
8155
- `);
8389
+ Address: ${address}
8390
+ Balance: ${balance} ETH
8391
+
8392
+ Use /track <address> for full forensic analysis`
8393
+ );
8394
+ } catch (error) {
8395
+ const msg = error instanceof Error ? error.message : String(error);
8396
+ await ctx.reply(`Wallet query failed: ${msg}`);
8397
+ }
8156
8398
  }
8157
- function rowToConfig(row) {
8158
- return {
8159
- id: row.id,
8160
- name: row.name,
8161
- strategy: row.strategy,
8162
- pairs: JSON.parse(row.pairs),
8163
- interval: row.interval_seconds,
8164
- createdAt: row.created_at,
8165
- updatedAt: row.updated_at
8166
- };
8399
+ async function handleAgentHelp(ctx) {
8400
+ const strategies = listStrategies();
8401
+ await ctx.reply(
8402
+ `\u{1F916} Agent Management
8403
+
8404
+ Commands:
8405
+ /agent_create <name> <strategy> <pairs> - Create agent
8406
+ /agent_list - List all agents
8407
+ /agent_start <name> - Start an agent
8408
+ /agent_stop <name> - Stop an agent
8409
+ /agent_status <name> - View status & decisions
8410
+ /agent_delete <name> - Delete an agent
8411
+
8412
+ Available strategies: ${strategies.join(", ")}
8413
+
8414
+ Example:
8415
+ /agent_create mybot momentum BTC,ETH`
8416
+ );
8167
8417
  }
8168
- var SqliteStore;
8169
- var init_sqlite_store = __esm({
8170
- "src/data/sqlite-store.ts"() {
8171
- "use strict";
8172
- init_cache();
8173
- SqliteStore = class {
8174
- // ---- Cache ---------------------------------------------------------------
8175
- async getCached(key) {
8176
- return getCached(key);
8177
- }
8178
- async setCache(key, value, ttlSeconds) {
8179
- setCache(key, value, ttlSeconds);
8180
- }
8181
- // ---- Agents --------------------------------------------------------------
8182
- async createAgent(config2) {
8183
- ensureAgentTables2();
8184
- getDb().prepare(
8185
- `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
8186
- VALUES (?, ?, ?, ?, ?, ?, ?)`
8187
- ).run(
8188
- config2.id,
8189
- config2.name,
8190
- config2.strategy,
8191
- JSON.stringify(config2.pairs),
8192
- config2.interval,
8193
- config2.createdAt,
8194
- config2.updatedAt
8195
- );
8196
- return config2;
8197
- }
8198
- async listAgents() {
8199
- ensureAgentTables2();
8200
- const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
8201
- return rows.map(rowToConfig);
8202
- }
8203
- async getAgentById(id) {
8204
- ensureAgentTables2();
8205
- const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
8206
- return row ? rowToConfig(row) : null;
8207
- }
8208
- async getAgentByName(name) {
8209
- ensureAgentTables2();
8210
- const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
8211
- return row ? rowToConfig(row) : null;
8212
- }
8213
- async deleteAgent(id) {
8214
- ensureAgentTables2();
8215
- const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
8216
- getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
8217
- return result.changes > 0;
8218
- }
8219
- async logDecision(result) {
8220
- ensureAgentTables2();
8221
- getDb().prepare(
8222
- `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
8223
- VALUES (?, ?, ?, ?, ?, ?, ?)`
8224
- ).run(
8225
- result.agentId,
8226
- result.symbol,
8227
- result.decision.action,
8228
- result.decision.confidence,
8229
- JSON.stringify(result.decision.reasoning),
8230
- JSON.stringify(result.signals),
8231
- result.timestamp
8232
- );
8233
- }
8234
- async getDecisions(agentId, limit) {
8235
- ensureAgentTables2();
8236
- const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
8237
- return rows.map((r) => ({
8238
- agentId: r.agent_id,
8239
- symbol: r.symbol,
8240
- timestamp: r.created_at,
8241
- signals: JSON.parse(r.signals),
8242
- decision: {
8243
- action: r.action,
8244
- confidence: r.confidence,
8245
- reasoning: JSON.parse(r.reasoning)
8246
- }
8247
- }));
8248
- }
8249
- // ---- Time-series (no-op for SQLite) --------------------------------------
8250
- async insertOHLCV(_records) {
8251
- }
8252
- async queryOHLCV(_symbol, _timeframe, _from, _to) {
8253
- return [];
8254
- }
8255
- // ---- Predictions (no-op for SQLite) --------------------------------------
8256
- async logPrediction(_prediction) {
8257
- }
8258
- async getPredictionAccuracy(_model, _days) {
8259
- return {
8260
- model: _model,
8261
- totalPredictions: 0,
8262
- correctPredictions: 0,
8263
- accuracy: 0,
8264
- byDirection: {
8265
- up: { total: 0, correct: 0, accuracy: 0 },
8266
- down: { total: 0, correct: 0, accuracy: 0 },
8267
- sideways: { total: 0, correct: 0, accuracy: 0 }
8268
- },
8269
- period: `${_days}d`
8270
- };
8271
- }
8272
- // ---- Lifecycle -----------------------------------------------------------
8273
- async close() {
8274
- }
8275
- };
8418
+ async function handleAgentCreate(ctx) {
8419
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8420
+ const name = args2[0];
8421
+ const strategy = args2[1] ?? "momentum";
8422
+ const pairsStr = args2[2] ?? "BTC,ETH";
8423
+ if (!name) {
8424
+ await ctx.reply(
8425
+ "Usage: /agent_create <name> <strategy> <pairs>\nExample: /agent_create mybot momentum BTC,ETH,SOL"
8426
+ );
8427
+ return;
8276
8428
  }
8277
- });
8429
+ try {
8430
+ const pairs = pairsStr.split(",").map((p) => p.trim().toUpperCase());
8431
+ const agent = createAgent(name, strategy, pairs);
8432
+ await ctx.reply(
8433
+ `\u2705 Agent Created
8278
8434
 
8279
- // src/data/postgres-store.ts
8280
- var postgres_store_exports = {};
8281
- __export(postgres_store_exports, {
8282
- PostgresStore: () => PostgresStore
8283
- });
8284
- import pg from "pg";
8285
- import { readFileSync as readFileSync2 } from "fs";
8286
- import { resolve as resolve2, dirname } from "path";
8287
- import { fileURLToPath } from "url";
8288
- var __filename, __dirname, PostgresStore;
8289
- var init_postgres_store = __esm({
8290
- "src/data/postgres-store.ts"() {
8291
- "use strict";
8292
- __filename = fileURLToPath(import.meta.url);
8293
- __dirname = dirname(__filename);
8294
- PostgresStore = class {
8295
- pool;
8296
- initialized = false;
8297
- constructor(connectionUrl) {
8298
- this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
8299
- }
8300
- async init() {
8301
- if (this.initialized) return;
8302
- const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
8303
- const sql = readFileSync2(migrationPath, "utf-8");
8304
- await this.pool.query(sql);
8305
- this.initialized = true;
8306
- }
8307
- async query(text, params) {
8308
- await this.init();
8309
- return this.pool.query(text, params);
8310
- }
8311
- // ---- Cache ---------------------------------------------------------------
8312
- async getCached(key) {
8313
- const now = Math.floor(Date.now() / 1e3);
8314
- const { rows } = await this.query(
8315
- "SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
8316
- [key, now]
8317
- );
8318
- if (rows.length === 0) return null;
8319
- return rows[0].value;
8320
- }
8321
- async setCache(key, value, ttlSeconds) {
8322
- const now = Math.floor(Date.now() / 1e3);
8323
- const expiresAt = now + ttlSeconds;
8324
- await this.query(
8325
- `INSERT INTO cache (key, value, expires_at, created_at)
8326
- VALUES ($1, $2, $3, $4)
8327
- ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
8328
- [key, JSON.stringify(value), expiresAt, now]
8329
- );
8330
- }
8331
- // ---- Agents --------------------------------------------------------------
8332
- async createAgent(config2) {
8333
- await this.query(
8334
- `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
8335
- VALUES ($1, $2, $3, $4, $5, $6, $7)`,
8336
- [
8337
- config2.id,
8338
- config2.name,
8339
- config2.strategy,
8340
- JSON.stringify(config2.pairs),
8341
- config2.interval,
8342
- config2.createdAt,
8343
- config2.updatedAt
8344
- ]
8345
- );
8346
- return config2;
8347
- }
8348
- async listAgents() {
8349
- const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
8350
- return rows.map((r) => ({
8351
- id: r.id,
8352
- name: r.name,
8353
- strategy: r.strategy,
8354
- pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
8355
- interval: r.interval_seconds,
8356
- createdAt: Number(r.created_at),
8357
- updatedAt: Number(r.updated_at)
8358
- }));
8359
- }
8360
- async getAgentById(id) {
8361
- const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
8362
- if (rows.length === 0) return null;
8363
- const r = rows[0];
8364
- return {
8365
- id: r.id,
8366
- name: r.name,
8367
- strategy: r.strategy,
8368
- pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
8369
- interval: r.interval_seconds,
8370
- createdAt: Number(r.created_at),
8371
- updatedAt: Number(r.updated_at)
8372
- };
8373
- }
8374
- async getAgentByName(name) {
8375
- const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
8376
- if (rows.length === 0) return null;
8377
- const r = rows[0];
8378
- return {
8379
- id: r.id,
8380
- name: r.name,
8381
- strategy: r.strategy,
8382
- pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
8383
- interval: r.interval_seconds,
8384
- createdAt: Number(r.created_at),
8385
- updatedAt: Number(r.updated_at)
8386
- };
8387
- }
8388
- async deleteAgent(id) {
8389
- const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
8390
- return (result.rowCount ?? 0) > 0;
8391
- }
8392
- async logDecision(result) {
8393
- await this.query(
8394
- `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
8395
- VALUES ($1, $2, $3, $4, $5, $6, $7)`,
8396
- [
8397
- result.agentId,
8398
- result.symbol,
8399
- result.decision.action,
8400
- result.decision.confidence,
8401
- JSON.stringify(result.decision.reasoning),
8402
- JSON.stringify(result.signals),
8403
- result.timestamp
8404
- ]
8405
- );
8406
- }
8407
- async getDecisions(agentId, limit) {
8408
- const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
8409
- agentId,
8410
- limit
8411
- ]);
8412
- return rows.map((r) => ({
8413
- agentId: r.agent_id,
8414
- symbol: r.symbol,
8415
- timestamp: Number(r.created_at),
8416
- signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
8417
- decision: {
8418
- action: r.action,
8419
- confidence: r.confidence,
8420
- reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
8421
- }
8422
- }));
8423
- }
8424
- // ---- Time-series ---------------------------------------------------------
8425
- async insertOHLCV(records) {
8426
- if (records.length === 0) return;
8427
- const values = [];
8428
- const placeholders = [];
8429
- for (let i = 0; i < records.length; i++) {
8430
- const r = records[i];
8431
- const offset = i * 9;
8432
- placeholders.push(
8433
- `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
8434
- );
8435
- values.push(
8436
- new Date(r.time).toISOString(),
8437
- r.symbol,
8438
- r.timeframe,
8439
- r.open,
8440
- r.high,
8441
- r.low,
8442
- r.close,
8443
- r.volume,
8444
- r.trades
8445
- );
8446
- }
8447
- await this.query(
8448
- `INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
8449
- VALUES ${placeholders.join(", ")}
8450
- ON CONFLICT (symbol, timeframe, time) DO UPDATE
8451
- SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
8452
- close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
8453
- values
8454
- );
8455
- }
8456
- async queryOHLCV(symbol, timeframe, from, to) {
8457
- const { rows } = await this.query(
8458
- `SELECT * FROM ohlcv
8459
- WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
8460
- ORDER BY time ASC`,
8461
- [symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
8462
- );
8463
- return rows.map((r) => ({
8464
- time: new Date(r.time).getTime(),
8465
- symbol: r.symbol,
8466
- timeframe: r.timeframe,
8467
- open: r.open,
8468
- high: r.high,
8469
- low: r.low,
8470
- close: r.close,
8471
- volume: r.volume,
8472
- trades: r.trades
8473
- }));
8474
- }
8475
- // ---- Predictions ---------------------------------------------------------
8476
- async logPrediction(prediction) {
8477
- await this.query(
8478
- `INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
8479
- VALUES ($1, $2, $3, $4, $5, $6, $7)`,
8480
- [
8481
- prediction.symbol,
8482
- prediction.model,
8483
- prediction.direction,
8484
- prediction.probability,
8485
- prediction.horizon,
8486
- JSON.stringify(prediction.features),
8487
- new Date(prediction.predictedAt).toISOString()
8488
- ]
8489
- );
8435
+ Name: ${agent.name}
8436
+ Strategy: ${agent.strategy}
8437
+ Pairs: ${agent.pairs.join(", ")}
8438
+ Interval: ${agent.interval}s
8439
+
8440
+ Use /agent_start ${agent.name} to activate`
8441
+ );
8442
+ } catch (error) {
8443
+ const msg = error instanceof Error ? error.message : String(error);
8444
+ await ctx.reply(`Failed to create agent: ${msg}`);
8445
+ }
8446
+ }
8447
+ async function handleAgentList(ctx) {
8448
+ const agents = listAgents();
8449
+ if (agents.length === 0) {
8450
+ await ctx.reply("No agents created yet. Use /agent_create to create one.");
8451
+ return;
8452
+ }
8453
+ let msg = "\u{1F916} Your Agents\n\n";
8454
+ for (const agent of agents) {
8455
+ const state = getAgentStatus(agent.id);
8456
+ const statusEmoji = state?.status === "running" ? "\u{1F7E2}" : state?.status === "stopped" ? "\u{1F534}" : "\u26AA";
8457
+ msg += `${statusEmoji} ${agent.name} [${state?.status ?? "idle"}]
8458
+ `;
8459
+ msg += ` Strategy: ${agent.strategy} | Pairs: ${agent.pairs.join(", ")}
8460
+ `;
8461
+ msg += ` Cycles: ${state?.cycleCount ?? 0} | Interval: ${agent.interval}s
8462
+
8463
+ `;
8464
+ }
8465
+ await ctx.reply(msg);
8466
+ }
8467
+ async function handleAgentStart(ctx) {
8468
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8469
+ const name = args2[0];
8470
+ if (!name) {
8471
+ await ctx.reply("Usage: /agent_start <name>");
8472
+ return;
8473
+ }
8474
+ try {
8475
+ const agent = getAgentByName(name);
8476
+ if (!agent) {
8477
+ await ctx.reply(`Agent "${name}" not found. Use /agent_list to see your agents.`);
8478
+ return;
8479
+ }
8480
+ const state = startAgent(agent.id);
8481
+ await ctx.reply(
8482
+ `\u{1F7E2} Agent "${state.config.name}" started. Monitoring ${state.config.pairs.join(", ")}.`
8483
+ );
8484
+ } catch (error) {
8485
+ const msg = error instanceof Error ? error.message : String(error);
8486
+ await ctx.reply(`Failed to start agent: ${msg}`);
8487
+ }
8488
+ }
8489
+ async function handleAgentStop(ctx) {
8490
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8491
+ const name = args2[0];
8492
+ if (!name) {
8493
+ await ctx.reply("Usage: /agent_stop <name>");
8494
+ return;
8495
+ }
8496
+ try {
8497
+ const agent = getAgentByName(name);
8498
+ if (!agent) {
8499
+ await ctx.reply(`Agent "${name}" not found.`);
8500
+ return;
8501
+ }
8502
+ const state = stopAgent(agent.id);
8503
+ await ctx.reply(`\u{1F534} Agent "${state.config.name}" stopped after ${state.cycleCount} cycles.`);
8504
+ } catch (error) {
8505
+ const msg = error instanceof Error ? error.message : String(error);
8506
+ await ctx.reply(`Failed to stop agent: ${msg}`);
8507
+ }
8508
+ }
8509
+ async function handleAgentStatus(ctx) {
8510
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8511
+ const name = args2[0];
8512
+ if (!name) {
8513
+ await ctx.reply("Usage: /agent_status <name>");
8514
+ return;
8515
+ }
8516
+ const agent = getAgentByName(name);
8517
+ if (!agent) {
8518
+ await ctx.reply(`Agent "${name}" not found.`);
8519
+ return;
8520
+ }
8521
+ const state = getAgentStatus(agent.id);
8522
+ if (!state) {
8523
+ await ctx.reply(`Agent "${name}" not found.`);
8524
+ return;
8525
+ }
8526
+ const statusEmoji = state.status === "running" ? "\u{1F7E2}" : state.status === "stopped" ? "\u{1F534}" : "\u26AA";
8527
+ let msg = `\u{1F916} Agent: ${state.config.name}
8528
+
8529
+ `;
8530
+ msg += `${statusEmoji} Status: ${state.status}
8531
+ `;
8532
+ msg += `Strategy: ${state.config.strategy}
8533
+ `;
8534
+ msg += `Pairs: ${state.config.pairs.join(", ")}
8535
+ `;
8536
+ msg += `Interval: ${state.config.interval}s
8537
+ `;
8538
+ msg += `Cycles: ${state.cycleCount}
8539
+ `;
8540
+ if (state.error) msg += `Error: ${state.error}
8541
+ `;
8542
+ const decisions = getRecentDecisions(agent.id, 5);
8543
+ if (decisions.length > 0) {
8544
+ msg += "\nRecent Decisions:\n";
8545
+ for (const d of decisions) {
8546
+ const actionEmoji = d.decision.action === "buy" ? "\u{1F7E2}" : d.decision.action === "sell" ? "\u{1F534}" : "\u26AA";
8547
+ const time = new Date(d.timestamp).toLocaleString();
8548
+ msg += `${actionEmoji} ${d.symbol} ${d.decision.action.toUpperCase()} (${d.decision.confidence}%) \u2014 ${time}
8549
+ `;
8550
+ if (d.decision.reasoning.length > 0) {
8551
+ msg += ` \u2192 ${d.decision.reasoning[0]}
8552
+ `;
8490
8553
  }
8491
- async getPredictionAccuracy(model, days) {
8492
- const since = new Date(Date.now() - days * 864e5).toISOString();
8493
- const { rows: totals } = await this.query(
8494
- `SELECT direction,
8495
- COUNT(*)::text AS total,
8496
- COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
8497
- FROM predictions
8498
- WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
8499
- GROUP BY direction`,
8500
- [model, since]
8501
- );
8502
- const byDir = {
8503
- up: { total: 0, correct: 0, accuracy: 0 },
8504
- down: { total: 0, correct: 0, accuracy: 0 },
8505
- sideways: { total: 0, correct: 0, accuracy: 0 }
8506
- };
8507
- let totalAll = 0;
8508
- let correctAll = 0;
8509
- for (const row of totals) {
8510
- const t = parseInt(row.total, 10);
8511
- const c = parseInt(row.correct, 10);
8512
- totalAll += t;
8513
- correctAll += c;
8514
- const dir = row.direction;
8515
- if (byDir[dir]) {
8516
- byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
8517
- }
8518
- }
8519
- return {
8520
- model,
8521
- totalPredictions: totalAll,
8522
- correctPredictions: correctAll,
8523
- accuracy: totalAll > 0 ? correctAll / totalAll : 0,
8524
- byDirection: byDir,
8525
- period: `${days}d`
8526
- };
8554
+ }
8555
+ }
8556
+ await ctx.reply(msg);
8557
+ }
8558
+ async function handleAgentDelete(ctx) {
8559
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8560
+ const name = args2[0];
8561
+ if (!name) {
8562
+ await ctx.reply("Usage: /agent_delete <name>");
8563
+ return;
8564
+ }
8565
+ try {
8566
+ const agent = getAgentByName(name);
8567
+ if (!agent) {
8568
+ await ctx.reply(`Agent "${name}" not found.`);
8569
+ return;
8570
+ }
8571
+ deleteAgent(agent.id);
8572
+ await ctx.reply(`\u{1F5D1} Agent "${name}" deleted.`);
8573
+ } catch (error) {
8574
+ const msg = error instanceof Error ? error.message : String(error);
8575
+ await ctx.reply(`Failed to delete agent: ${msg}`);
8576
+ }
8577
+ }
8578
+ var init_commands2 = __esm({
8579
+ "src/telegram/commands/index.ts"() {
8580
+ "use strict";
8581
+ init_registry();
8582
+ init_loader();
8583
+ init_project_analyzer();
8584
+ init_risk_scorer();
8585
+ init_wallet_analyzer();
8586
+ init_contract_auditor();
8587
+ init_market();
8588
+ init_ico_tracker();
8589
+ init_defillama();
8590
+ init_binance();
8591
+ init_predictor();
8592
+ init_agent();
8593
+ init_market2();
8594
+ }
8595
+ });
8596
+
8597
+ // src/telegram/middleware/rate-limit.ts
8598
+ async function rateLimitMiddleware(ctx, next) {
8599
+ const userId = ctx.from?.id;
8600
+ if (!userId) {
8601
+ await next();
8602
+ return;
8603
+ }
8604
+ const now = Date.now();
8605
+ const entry = userLimits2.get(userId);
8606
+ if (!entry || now >= entry.resetAt) {
8607
+ userLimits2.set(userId, { count: 1, resetAt: now + WINDOW_MS2 });
8608
+ await next();
8609
+ return;
8610
+ }
8611
+ if (entry.count >= MAX_REQUESTS2) {
8612
+ await ctx.reply("Rate limited. Please wait a moment before sending more commands.");
8613
+ return;
8614
+ }
8615
+ entry.count++;
8616
+ await next();
8617
+ }
8618
+ function startRateLimitCleanup2(intervalMs = 3e5) {
8619
+ return setInterval(() => {
8620
+ const now = Date.now();
8621
+ for (const [userId, entry] of userLimits2) {
8622
+ if (now >= entry.resetAt) {
8623
+ userLimits2.delete(userId);
8527
8624
  }
8528
- // ---- Lifecycle -----------------------------------------------------------
8529
- async close() {
8530
- await this.pool.end();
8625
+ }
8626
+ }, intervalMs);
8627
+ }
8628
+ var userLimits2, MAX_REQUESTS2, WINDOW_MS2;
8629
+ var init_rate_limit2 = __esm({
8630
+ "src/telegram/middleware/rate-limit.ts"() {
8631
+ "use strict";
8632
+ userLimits2 = /* @__PURE__ */ new Map();
8633
+ MAX_REQUESTS2 = 10;
8634
+ WINDOW_MS2 = 6e4;
8635
+ }
8636
+ });
8637
+
8638
+ // src/telegram/bot.ts
8639
+ var bot_exports2 = {};
8640
+ __export(bot_exports2, {
8641
+ startTelegramBot: () => startTelegramBot
8642
+ });
8643
+ import { Bot } from "grammy";
8644
+ async function startTelegramBot() {
8645
+ const config2 = loadConfig();
8646
+ const token = config2.telegramToken;
8647
+ if (!token) {
8648
+ throw new Error("Telegram token not configured. Run: vizzor config set telegramToken <token>");
8649
+ }
8650
+ setConfig(config2);
8651
+ setToolHandler(handleTool);
8652
+ const bot = new Bot(token);
8653
+ bot.use(rateLimitMiddleware);
8654
+ registerCommands(bot);
8655
+ bot.on("message:text", async (ctx) => {
8656
+ const text = ctx.message.text;
8657
+ if (text.startsWith("/")) return;
8658
+ await ctx.reply("\u{1F52E} Analyzing...");
8659
+ try {
8660
+ const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
8661
+ const chunks = splitMessage(response, 4e3);
8662
+ for (const chunk of chunks) {
8663
+ await ctx.reply(chunk);
8531
8664
  }
8532
- };
8665
+ } catch (error) {
8666
+ const msg = error instanceof Error ? error.message : String(error);
8667
+ await ctx.reply(`Analysis failed: ${msg}`);
8668
+ }
8669
+ });
8670
+ bot.catch((err) => {
8671
+ logger2.error({ err: err.message }, "Telegram bot error");
8672
+ });
8673
+ const cleanupInterval = startRateLimitCleanup2();
8674
+ logger2.info("Telegram bot starting...");
8675
+ await bot.start({
8676
+ onStart: (botInfo) => {
8677
+ logger2.info(`Telegram bot started as @${botInfo.username}`);
8678
+ }
8679
+ });
8680
+ clearInterval(cleanupInterval);
8681
+ }
8682
+ var logger2;
8683
+ var init_bot2 = __esm({
8684
+ "src/telegram/bot.ts"() {
8685
+ "use strict";
8686
+ init_loader();
8687
+ init_client2();
8688
+ init_tool_handler();
8689
+ init_tools();
8690
+ init_chat();
8691
+ init_message_split();
8692
+ init_commands2();
8693
+ init_rate_limit2();
8694
+ init_logger();
8695
+ logger2 = createLogger("telegram-bot");
8533
8696
  }
8534
8697
  });
8535
8698
 
8536
- // src/data/store-factory.ts
8537
- async function getStore(config2) {
8538
- if (instance) return instance;
8539
- if (config2.database?.type === "postgres" && config2.database.url) {
8540
- const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
8541
- instance = new PostgresStore2(config2.database.url);
8699
+ // src/cli/commands/bot.ts
8700
+ var bot_exports3 = {};
8701
+ __export(bot_exports3, {
8702
+ handleBotStart: () => handleBotStart,
8703
+ handleBotValidate: () => handleBotValidate
8704
+ });
8705
+ import chalk7 from "chalk";
8706
+ async function handleBotStart(options) {
8707
+ const config2 = getConfig();
8708
+ const startDiscord = options.discord || options.all;
8709
+ const startTelegram = options.telegram || options.all;
8710
+ if (!startDiscord && !startTelegram) {
8711
+ console.log(chalk7.yellow("Specify which bot to start:"));
8712
+ console.log(" --discord Start Discord bot");
8713
+ console.log(" --telegram Start Telegram bot");
8714
+ console.log(" --all Start all bots");
8715
+ return;
8716
+ }
8717
+ const promises = [];
8718
+ if (startDiscord) {
8719
+ requireKey("DISCORD_TOKEN", config2.discordToken);
8720
+ console.log(chalk7.blue("Starting Discord bot..."));
8721
+ const { startDiscordBot: startDiscordBot2 } = await Promise.resolve().then(() => (init_bot(), bot_exports));
8722
+ promises.push(startDiscordBot2());
8723
+ }
8724
+ if (startTelegram) {
8725
+ requireKey("TELEGRAM_BOT_TOKEN", config2.telegramToken);
8726
+ console.log(chalk7.blue("Starting Telegram bot..."));
8727
+ const { startTelegramBot: startTelegramBot2 } = await Promise.resolve().then(() => (init_bot2(), bot_exports2));
8728
+ promises.push(startTelegramBot2());
8729
+ }
8730
+ const shutdown = () => {
8731
+ console.log(chalk7.dim("\nShutting down bots..."));
8732
+ process.exit(0);
8733
+ };
8734
+ process.on("SIGINT", shutdown);
8735
+ process.on("SIGTERM", shutdown);
8736
+ await Promise.all(promises);
8737
+ }
8738
+ function handleBotValidate() {
8739
+ const config2 = getConfig();
8740
+ console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
8741
+ const checks = [
8742
+ { label: "Anthropic API Key", isSet: hasKey(config2.anthropicApiKey), required: true },
8743
+ { label: "Etherscan API Key", isSet: hasKey(config2.etherscanApiKey), required: true },
8744
+ { label: "Discord Token", isSet: hasKey(config2.discordToken), required: false },
8745
+ { label: "Discord Guild ID", isSet: hasKey(config2.discordGuildId), required: false },
8746
+ { label: "Telegram Token", isSet: hasKey(config2.telegramToken), required: false },
8747
+ { label: "CryptoPanic Key", isSet: hasKey(config2.cryptopanicApiKey), required: false }
8748
+ ];
8749
+ let allRequired = true;
8750
+ for (const check of checks) {
8751
+ const status = check.isSet ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
8752
+ console.log(` ${status.padEnd(18)} ${check.label}`);
8753
+ if (check.required && !check.isSet) allRequired = false;
8754
+ }
8755
+ console.log();
8756
+ if (hasKey(config2.discordToken)) {
8757
+ console.log(chalk7.green(" Discord bot: Ready to start"));
8542
8758
  } else {
8543
- instance = new SqliteStore();
8759
+ console.log(chalk7.yellow(" Discord bot: Set discordToken to enable"));
8760
+ console.log(chalk7.dim(" vizzor config set discordToken <token>"));
8761
+ }
8762
+ if (hasKey(config2.telegramToken)) {
8763
+ console.log(chalk7.green(" Telegram bot: Ready to start"));
8764
+ } else {
8765
+ console.log(chalk7.yellow(" Telegram bot: Set telegramToken to enable"));
8766
+ console.log(chalk7.dim(" vizzor config set telegramToken <token>"));
8767
+ }
8768
+ console.log();
8769
+ if (!allRequired) {
8770
+ console.log(chalk7.red("Required keys are missing. Run: vizzor config set <key> <value>"));
8771
+ } else if (hasKey(config2.discordToken) && hasKey(config2.telegramToken)) {
8772
+ console.log(chalk7.green("All bots ready. Run: vizzor bot start --all"));
8773
+ } else if (hasKey(config2.discordToken) || hasKey(config2.telegramToken)) {
8774
+ const which = hasKey(config2.discordToken) ? "--discord" : "--telegram";
8775
+ console.log(chalk7.green(`Bot ready. Run: vizzor bot start ${which}`));
8544
8776
  }
8545
- return instance;
8546
8777
  }
8547
- var instance;
8548
- var init_store_factory = __esm({
8549
- "src/data/store-factory.ts"() {
8778
+ var init_bot3 = __esm({
8779
+ "src/cli/commands/bot.ts"() {
8550
8780
  "use strict";
8551
- init_sqlite_store();
8552
- instance = null;
8781
+ init_loader();
8782
+ init_keys();
8553
8783
  }
8554
8784
  });
8555
8785
 
8556
8786
  // src/data/collector.ts
8557
- var log, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
8787
+ var log2, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
8558
8788
  var init_collector = __esm({
8559
8789
  "src/data/collector.ts"() {
8560
8790
  "use strict";
8561
8791
  init_binance();
8562
8792
  init_logger();
8563
- log = createLogger("collector");
8793
+ log2 = createLogger("collector");
8564
8794
  MAJOR_SYMBOLS = [
8565
8795
  "BTC",
8566
8796
  "ETH",
@@ -8611,10 +8841,10 @@ var init_collector = __esm({
8611
8841
  }
8612
8842
  start() {
8613
8843
  if (this.status.running) {
8614
- log.warn("Collector already running");
8844
+ log2.warn("Collector already running");
8615
8845
  return;
8616
8846
  }
8617
- log.info(
8847
+ log2.info(
8618
8848
  `Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
8619
8849
  );
8620
8850
  this.status.running = true;
@@ -8627,10 +8857,10 @@ var init_collector = __esm({
8627
8857
  this.timer = null;
8628
8858
  }
8629
8859
  this.status.running = false;
8630
- log.info("Data collector stopped");
8860
+ log2.info("Data collector stopped");
8631
8861
  }
8632
8862
  async collectAll() {
8633
- log.info("Collection cycle starting");
8863
+ log2.info("Collection cycle starting");
8634
8864
  const start = Date.now();
8635
8865
  for (const timeframe of TIMEFRAMES) {
8636
8866
  for (const symbol of this.symbols) {
@@ -8638,7 +8868,7 @@ var init_collector = __esm({
8638
8868
  await this.collectSymbol(symbol, timeframe);
8639
8869
  } catch (err) {
8640
8870
  this.status.errors++;
8641
- log.error(
8871
+ log2.error(
8642
8872
  `Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
8643
8873
  );
8644
8874
  }
@@ -8646,7 +8876,7 @@ var init_collector = __esm({
8646
8876
  }
8647
8877
  this.status.lastRun = Date.now();
8648
8878
  const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
8649
- log.info(
8879
+ log2.info(
8650
8880
  `Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
8651
8881
  );
8652
8882
  }
@@ -8733,6 +8963,481 @@ var init_collect = __esm({
8733
8963
  }
8734
8964
  });
8735
8965
 
8966
+ // src/api/routes/v1/market.ts
8967
+ async function registerMarketRoutes(server) {
8968
+ server.get("/price/:symbol", {
8969
+ schema: {
8970
+ tags: ["Market"],
8971
+ summary: "Get live price and market data for a symbol",
8972
+ params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
8973
+ },
8974
+ handler: async (request) => {
8975
+ const { symbol } = request.params;
8976
+ return handleTool("get_market_data", { symbol });
8977
+ }
8978
+ });
8979
+ server.get("/trending", {
8980
+ schema: {
8981
+ tags: ["Market"],
8982
+ summary: "Get trending tokens from DEX and CoinGecko"
8983
+ },
8984
+ handler: async () => {
8985
+ return handleTool("get_trending", {});
8986
+ }
8987
+ });
8988
+ server.get("/fear-greed", {
8989
+ schema: {
8990
+ tags: ["Market"],
8991
+ summary: "Get Crypto Fear & Greed Index with history"
8992
+ },
8993
+ handler: async () => {
8994
+ return handleTool("get_fear_greed", {});
8995
+ }
8996
+ });
8997
+ server.get("/news", {
8998
+ schema: {
8999
+ tags: ["Market"],
9000
+ summary: "Get latest crypto news with sentiment",
9001
+ querystring: {
9002
+ type: "object",
9003
+ properties: { symbol: { type: "string" } }
9004
+ }
9005
+ },
9006
+ handler: async (request) => {
9007
+ const { symbol } = request.query;
9008
+ return handleTool("get_crypto_news", { symbol });
9009
+ }
9010
+ });
9011
+ server.get("/dex/search", {
9012
+ schema: {
9013
+ tags: ["Market"],
9014
+ summary: "Search tokens on decentralized exchanges",
9015
+ querystring: {
9016
+ type: "object",
9017
+ properties: { q: { type: "string" } },
9018
+ required: ["q"]
9019
+ }
9020
+ },
9021
+ handler: async (request) => {
9022
+ const { q } = request.query;
9023
+ return handleTool("search_token_dex", { query: q });
9024
+ }
9025
+ });
9026
+ server.get("/derivatives/:symbol", {
9027
+ schema: {
9028
+ tags: ["Market"],
9029
+ summary: "Get derivatives data (funding rate, open interest)",
9030
+ params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
9031
+ },
9032
+ handler: async (request) => {
9033
+ const { symbol } = request.params;
9034
+ return handleTool("get_derivatives_data", { symbol });
9035
+ }
9036
+ });
9037
+ }
9038
+ var init_market3 = __esm({
9039
+ "src/api/routes/v1/market.ts"() {
9040
+ "use strict";
9041
+ init_tool_handler();
9042
+ }
9043
+ });
9044
+
9045
+ // src/api/routes/v1/analysis.ts
9046
+ async function registerAnalysisRoutes(server) {
9047
+ server.get("/technical/:symbol", {
9048
+ schema: {
9049
+ tags: ["Analysis"],
9050
+ summary: "Run technical analysis on a symbol",
9051
+ params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] },
9052
+ querystring: {
9053
+ type: "object",
9054
+ properties: { timeframe: { type: "string", default: "4h" } }
9055
+ }
9056
+ },
9057
+ handler: async (request) => {
9058
+ const { symbol } = request.params;
9059
+ const { timeframe } = request.query;
9060
+ return handleTool("get_technical_analysis", { symbol, timeframe });
9061
+ }
9062
+ });
9063
+ server.get("/prediction/:symbol", {
9064
+ schema: {
9065
+ tags: ["Analysis"],
9066
+ summary: "Generate multi-signal composite prediction",
9067
+ params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
9068
+ },
9069
+ handler: async (request) => {
9070
+ const { symbol } = request.params;
9071
+ return handleTool("get_prediction", { symbol });
9072
+ }
9073
+ });
9074
+ server.get("/ml/:symbol", {
9075
+ schema: {
9076
+ tags: ["Analysis"],
9077
+ summary: "Get ML-enhanced prediction from sidecar models",
9078
+ params: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
9079
+ },
9080
+ handler: async (request) => {
9081
+ const { symbol } = request.params;
9082
+ return handleTool("get_ml_prediction", { symbol });
9083
+ }
9084
+ });
9085
+ server.get("/raises/recent", {
9086
+ schema: {
9087
+ tags: ["Analysis"],
9088
+ summary: "Get recent crypto fundraising rounds",
9089
+ querystring: {
9090
+ type: "object",
9091
+ properties: {
9092
+ category: { type: "string" },
9093
+ chain: { type: "string" }
9094
+ }
9095
+ }
9096
+ },
9097
+ handler: async (request) => {
9098
+ const { category, chain } = request.query;
9099
+ return handleTool("get_raises", { category, chain });
9100
+ }
9101
+ });
9102
+ }
9103
+ var init_analysis = __esm({
9104
+ "src/api/routes/v1/analysis.ts"() {
9105
+ "use strict";
9106
+ init_tool_handler();
9107
+ }
9108
+ });
9109
+
9110
+ // src/api/routes/v1/security.ts
9111
+ async function registerSecurityRoutes(server) {
9112
+ server.post("/token", {
9113
+ schema: {
9114
+ tags: ["Security"],
9115
+ summary: "Check token security via GoPlus",
9116
+ body: {
9117
+ type: "object",
9118
+ properties: {
9119
+ address: { type: "string" },
9120
+ chain: { type: "string", default: "ethereum" }
9121
+ },
9122
+ required: ["address"]
9123
+ }
9124
+ },
9125
+ handler: async (request) => {
9126
+ const { address, chain } = request.body;
9127
+ return handleTool("get_token_security", { address, chain });
9128
+ }
9129
+ });
9130
+ server.post("/rug-check", {
9131
+ schema: {
9132
+ tags: ["Security"],
9133
+ summary: "Check token for rug pull indicators",
9134
+ body: {
9135
+ type: "object",
9136
+ properties: {
9137
+ address: { type: "string" },
9138
+ chain: { type: "string", default: "ethereum" }
9139
+ },
9140
+ required: ["address"]
9141
+ }
9142
+ },
9143
+ handler: async (request) => {
9144
+ const { address, chain } = request.body;
9145
+ return handleTool("check_rug_indicators", { address, chain });
9146
+ }
9147
+ });
9148
+ server.post("/wallet", {
9149
+ schema: {
9150
+ tags: ["Security"],
9151
+ summary: "Analyze a wallet address",
9152
+ body: {
9153
+ type: "object",
9154
+ properties: {
9155
+ address: { type: "string" },
9156
+ chain: { type: "string", default: "ethereum" }
9157
+ },
9158
+ required: ["address"]
9159
+ }
9160
+ },
9161
+ handler: async (request) => {
9162
+ const { address, chain } = request.body;
9163
+ return handleTool("analyze_wallet", { address, chain });
9164
+ }
9165
+ });
9166
+ }
9167
+ var init_security = __esm({
9168
+ "src/api/routes/v1/security.ts"() {
9169
+ "use strict";
9170
+ init_tool_handler();
9171
+ }
9172
+ });
9173
+
9174
+ // src/api/auth/keys.ts
9175
+ import { randomBytes, scryptSync } from "crypto";
9176
+ function hashApiKey(key) {
9177
+ return scryptSync(key, API_KEY_SALT, 64).toString("hex");
9178
+ }
9179
+ function ensureKeysTable() {
9180
+ getDb().exec(`
9181
+ CREATE TABLE IF NOT EXISTS api_keys (
9182
+ id TEXT PRIMARY KEY,
9183
+ label TEXT NOT NULL,
9184
+ key_hash TEXT NOT NULL UNIQUE,
9185
+ key_prefix TEXT NOT NULL,
9186
+ rate_limit INTEGER NOT NULL DEFAULT 100,
9187
+ created_at INTEGER NOT NULL,
9188
+ revoked_at INTEGER
9189
+ )
9190
+ `);
9191
+ }
9192
+ function createApiKey(label) {
9193
+ ensureKeysTable();
9194
+ const rawKey = `vzr_${randomBytes(32).toString("hex")}`;
9195
+ const keyHash = hashApiKey(rawKey);
9196
+ const keyPrefix = rawKey.slice(0, 12) + "...";
9197
+ const id = randomBytes(16).toString("hex");
9198
+ const now = Date.now();
9199
+ getDb().prepare(
9200
+ `INSERT INTO api_keys (id, label, key_hash, key_prefix, rate_limit, created_at)
9201
+ VALUES (?, ?, ?, ?, ?, ?)`
9202
+ ).run(id, label, keyHash, keyPrefix, 100, now);
9203
+ return {
9204
+ key: rawKey,
9205
+ record: { id, label, keyPrefix, rateLimit: 100, createdAt: now, revokedAt: null }
9206
+ };
9207
+ }
9208
+ function listApiKeys() {
9209
+ ensureKeysTable();
9210
+ const rows = getDb().prepare("SELECT * FROM api_keys WHERE revoked_at IS NULL ORDER BY created_at DESC").all();
9211
+ return rows.map((r) => ({
9212
+ id: r.id,
9213
+ label: r.label,
9214
+ keyPrefix: r.key_prefix,
9215
+ rateLimit: r.rate_limit,
9216
+ createdAt: r.created_at,
9217
+ revokedAt: r.revoked_at
9218
+ }));
9219
+ }
9220
+ function revokeApiKey(id) {
9221
+ ensureKeysTable();
9222
+ const result = getDb().prepare("UPDATE api_keys SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL").run(Date.now(), id);
9223
+ return result.changes > 0;
9224
+ }
9225
+ var API_KEY_SALT;
9226
+ var init_keys2 = __esm({
9227
+ "src/api/auth/keys.ts"() {
9228
+ "use strict";
9229
+ init_cache();
9230
+ API_KEY_SALT = "vizzor-api-key-v1";
9231
+ }
9232
+ });
9233
+
9234
+ // src/api/auth/middleware.ts
9235
+ async function authMiddleware(request, reply) {
9236
+ if (PUBLIC_PATHS.some((p) => request.url === p) || request.url.startsWith("/docs/")) {
9237
+ return;
9238
+ }
9239
+ const apiKey = request.headers["x-api-key"];
9240
+ if (!apiKey) {
9241
+ return reply.status(401).send({
9242
+ error: "Unauthorized",
9243
+ message: "Missing X-API-Key header"
9244
+ });
9245
+ }
9246
+ const keyHash = hashApiKey(apiKey);
9247
+ const valid = await validateKey2(keyHash);
9248
+ if (!valid) {
9249
+ return reply.status(403).send({
9250
+ error: "Forbidden",
9251
+ message: "Invalid API key"
9252
+ });
9253
+ }
9254
+ }
9255
+ async function validateKey2(keyHash) {
9256
+ const store = getStoreInstance();
9257
+ if (!store) return false;
9258
+ const cached = await store.getCached(`apikey:${keyHash}`);
9259
+ if (cached !== null) return cached.valid;
9260
+ return true;
9261
+ }
9262
+ var PUBLIC_PATHS;
9263
+ var init_middleware = __esm({
9264
+ "src/api/auth/middleware.ts"() {
9265
+ "use strict";
9266
+ init_keys2();
9267
+ init_store_factory();
9268
+ PUBLIC_PATHS = ["/health", "/docs", "/docs/"];
9269
+ }
9270
+ });
9271
+
9272
+ // src/api/middleware/error-handler.ts
9273
+ function errorHandler(error, _request, reply) {
9274
+ if (error.statusCode === 429) {
9275
+ void reply.status(429).send({
9276
+ error: "Too Many Requests",
9277
+ message: "Rate limit exceeded. Try again later."
9278
+ });
9279
+ return;
9280
+ }
9281
+ const status = error.statusCode ?? 500;
9282
+ void reply.status(status).send({
9283
+ error: error.name ?? "InternalError",
9284
+ message: status >= 500 ? "Internal server error" : error.message
9285
+ });
9286
+ }
9287
+ var init_error_handler = __esm({
9288
+ "src/api/middleware/error-handler.ts"() {
9289
+ "use strict";
9290
+ }
9291
+ });
9292
+
9293
+ // src/api/server.ts
9294
+ var server_exports = {};
9295
+ __export(server_exports, {
9296
+ startApiServer: () => startApiServer
9297
+ });
9298
+ import Fastify from "fastify";
9299
+ import cors from "@fastify/cors";
9300
+ import rateLimit from "@fastify/rate-limit";
9301
+ import swagger from "@fastify/swagger";
9302
+ import swaggerUi from "@fastify/swagger-ui";
9303
+ async function startApiServer(options) {
9304
+ const server = Fastify({ logger: false });
9305
+ await server.register(cors, { origin: true });
9306
+ await server.register(rateLimit, {
9307
+ max: 100,
9308
+ timeWindow: "1 minute"
9309
+ });
9310
+ await server.register(swagger, {
9311
+ openapi: {
9312
+ info: {
9313
+ title: "Vizzor API",
9314
+ description: "AI-powered crypto intelligence REST API",
9315
+ version: "0.7.0"
9316
+ },
9317
+ servers: [{ url: `http://${options.host}:${options.port}` }],
9318
+ components: {
9319
+ securitySchemes: {
9320
+ apiKey: {
9321
+ type: "apiKey",
9322
+ name: "X-API-Key",
9323
+ in: "header"
9324
+ }
9325
+ }
9326
+ }
9327
+ }
9328
+ });
9329
+ await server.register(swaggerUi, {
9330
+ routePrefix: "/docs"
9331
+ });
9332
+ if (options.enableAuth) {
9333
+ server.addHook("onRequest", authMiddleware);
9334
+ }
9335
+ server.setErrorHandler(errorHandler);
9336
+ server.get("/health", async () => ({
9337
+ status: "ok",
9338
+ version: "0.7.0",
9339
+ uptime: process.uptime(),
9340
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
9341
+ }));
9342
+ await server.register(registerMarketRoutes, { prefix: "/v1/market" });
9343
+ await server.register(registerAnalysisRoutes, { prefix: "/v1/analysis" });
9344
+ await server.register(registerSecurityRoutes, { prefix: "/v1/security" });
9345
+ await server.listen({ port: options.port, host: options.host });
9346
+ log3.info(`Vizzor API listening on ${options.host}:${options.port}`);
9347
+ log3.info(`OpenAPI docs at http://${options.host}:${options.port}/docs`);
9348
+ }
9349
+ var log3;
9350
+ var init_server = __esm({
9351
+ "src/api/server.ts"() {
9352
+ "use strict";
9353
+ init_logger();
9354
+ init_market3();
9355
+ init_analysis();
9356
+ init_security();
9357
+ init_middleware();
9358
+ init_error_handler();
9359
+ log3 = createLogger("api");
9360
+ }
9361
+ });
9362
+
9363
+ // src/cli/commands/serve.ts
9364
+ var serve_exports = {};
9365
+ __export(serve_exports, {
9366
+ handleServe: () => handleServe
9367
+ });
9368
+ import chalk9 from "chalk";
9369
+ async function handleServe(options) {
9370
+ console.log(chalk9.bold("Starting Vizzor REST API..."));
9371
+ const { startApiServer: startApiServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
9372
+ await startApiServer2({
9373
+ port: options.port,
9374
+ host: options.host,
9375
+ enableAuth: options.auth
9376
+ });
9377
+ console.log(chalk9.green(`API running on http://${options.host}:${options.port}`));
9378
+ console.log(chalk9.dim(`Docs: http://${options.host}:${options.port}/docs`));
9379
+ console.log(chalk9.dim("\nPress Ctrl+C to stop"));
9380
+ process.on("SIGINT", () => {
9381
+ console.log(chalk9.yellow("\nShutting down..."));
9382
+ process.exit(0);
9383
+ });
9384
+ await new Promise(() => {
9385
+ });
9386
+ }
9387
+ var init_serve = __esm({
9388
+ "src/cli/commands/serve.ts"() {
9389
+ "use strict";
9390
+ }
9391
+ });
9392
+
9393
+ // src/cli/commands/api.ts
9394
+ var api_exports = {};
9395
+ __export(api_exports, {
9396
+ handleApiKeyCreate: () => handleApiKeyCreate,
9397
+ handleApiKeyList: () => handleApiKeyList,
9398
+ handleApiKeyRevoke: () => handleApiKeyRevoke
9399
+ });
9400
+ import chalk10 from "chalk";
9401
+ function handleApiKeyCreate(label) {
9402
+ const { key, record } = createApiKey(label || "default");
9403
+ console.log(chalk10.green("API key created successfully!"));
9404
+ process.stdout.write(chalk10.bold(`
9405
+ Key: ${key}
9406
+
9407
+ `));
9408
+ console.log(chalk10.yellow(" Save this key \u2014 it will not be shown again."));
9409
+ console.log(` Label: ${record.label}`);
9410
+ console.log(` ID: ${record.id}`);
9411
+ }
9412
+ function handleApiKeyList() {
9413
+ const keys = listApiKeys();
9414
+ if (keys.length === 0) {
9415
+ console.log(chalk10.dim("No API keys found. Create one with: vizzor api key create [label]"));
9416
+ return;
9417
+ }
9418
+ console.log(chalk10.bold("Active API Keys\n"));
9419
+ for (const k of keys) {
9420
+ console.log(` ${chalk10.cyan(k.keyPrefix)} ${k.label} (${k.id.slice(0, 8)})`);
9421
+ console.log(` Rate limit: ${k.rateLimit} req/min`);
9422
+ console.log(` Created: ${new Date(k.createdAt).toISOString()}
9423
+ `);
9424
+ }
9425
+ }
9426
+ function handleApiKeyRevoke(id) {
9427
+ const revoked = revokeApiKey(id);
9428
+ if (revoked) {
9429
+ console.log(chalk10.green(`API key ${id} revoked.`));
9430
+ } else {
9431
+ console.log(chalk10.red(`No active key found with ID: ${id}`));
9432
+ }
9433
+ }
9434
+ var init_api = __esm({
9435
+ "src/cli/commands/api.ts"() {
9436
+ "use strict";
9437
+ init_keys2();
9438
+ }
9439
+ });
9440
+
8736
9441
  // src/tui/components/status-bar.tsx
8737
9442
  import React, { useState, useEffect } from "react";
8738
9443
  import { Box, Text, Spacer } from "ink";
@@ -9473,7 +10178,7 @@ function useAIStream() {
9473
10178
  var init_use_ai_stream = __esm({
9474
10179
  "src/tui/hooks/use-ai-stream.ts"() {
9475
10180
  "use strict";
9476
- init_client();
10181
+ init_client2();
9477
10182
  init_chat();
9478
10183
  init_tools();
9479
10184
  init_context_injector();
@@ -9908,8 +10613,8 @@ async function handleConfig(args2) {
9908
10613
  }
9909
10614
  const isSensitive = key.toLowerCase().includes("key") || key.toLowerCase().includes("token");
9910
10615
  if (isSensitive) {
9911
- const { validateKey: validateKey2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
9912
- const error = validateKey2(key, value);
10616
+ const { validateKey: validateKey3 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
10617
+ const error = validateKey3(key, value);
9913
10618
  if (error && !error.startsWith("Warning:")) {
9914
10619
  return { blocks: [], text: `Rejected: ${error}` };
9915
10620
  }
@@ -10025,7 +10730,7 @@ var init_commands3 = __esm({
10025
10730
  init_constants();
10026
10731
  init_binance();
10027
10732
  init_agent();
10028
- init_client();
10733
+ init_client2();
10029
10734
  init_registry2();
10030
10735
  init_types();
10031
10736
  }
@@ -10467,7 +11172,7 @@ var init_app = __esm({
10467
11172
  init_commands3();
10468
11173
  init_loader();
10469
11174
  init_constants();
10470
- init_client();
11175
+ init_client2();
10471
11176
  init_tool_handler();
10472
11177
  init_tools();
10473
11178
  init_binance();
@@ -10539,6 +11244,24 @@ collectCmd.command("status").description("Show data collection status").action(a
10539
11244
  const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
10540
11245
  handleCollectStatus2();
10541
11246
  });
11247
+ 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) => {
11248
+ const { handleServe: handleServe2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
11249
+ await handleServe2(options);
11250
+ });
11251
+ var apiCmd = program.command("api").description("API management");
11252
+ var apiKeyCmd = apiCmd.command("key").description("API key management");
11253
+ apiKeyCmd.command("create [label]").description("Generate a new API key").action(async (label) => {
11254
+ const { handleApiKeyCreate: handleApiKeyCreate2 } = await Promise.resolve().then(() => (init_api(), api_exports));
11255
+ handleApiKeyCreate2(label);
11256
+ });
11257
+ apiKeyCmd.command("list").description("List active API keys").action(async () => {
11258
+ const { handleApiKeyList: handleApiKeyList2 } = await Promise.resolve().then(() => (init_api(), api_exports));
11259
+ handleApiKeyList2();
11260
+ });
11261
+ apiKeyCmd.command("revoke <id>").description("Revoke an API key").action(async (id) => {
11262
+ const { handleApiKeyRevoke: handleApiKeyRevoke2 } = await Promise.resolve().then(() => (init_api(), api_exports));
11263
+ handleApiKeyRevoke2(id);
11264
+ });
10542
11265
  var args = process.argv.slice(2);
10543
11266
  if (args.length === 0) {
10544
11267
  await loadConfig();