@vizzor/cli 0.5.0 → 0.6.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,15 @@ 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
+ })),
53
62
  discordToken: z.string().optional(),
54
63
  discordGuildId: z.string().optional(),
55
64
  telegramToken: z.string().optional()
@@ -647,12 +656,12 @@ var init_adapter = __esm({
647
656
  toBlock: options?.toBlock,
648
657
  args: options?.args
649
658
  });
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
659
+ return logs.map((log3) => ({
660
+ eventName: log3.eventName ?? eventName,
661
+ blockNumber: log3.blockNumber ?? 0n,
662
+ transactionHash: log3.transactionHash ?? "0x",
663
+ args: log3.args ?? {},
664
+ logIndex: log3.logIndex ?? 0
656
665
  }));
657
666
  }
658
667
  // ── Tokens ──────────────────────────────────────────────────────────────
@@ -1714,6 +1723,111 @@ var init_fear_greed = __esm({
1714
1723
  }
1715
1724
  });
1716
1725
 
1726
+ // src/utils/logger.ts
1727
+ import pino from "pino";
1728
+ function createLogger(name) {
1729
+ const isDev = process.env["NODE_ENV"] !== "production";
1730
+ if (isDev) {
1731
+ return pino({
1732
+ name,
1733
+ level,
1734
+ transport: {
1735
+ target: "pino-pretty",
1736
+ options: {
1737
+ colorize: true
1738
+ }
1739
+ }
1740
+ });
1741
+ }
1742
+ return pino({ name, level });
1743
+ }
1744
+ var level;
1745
+ var init_logger = __esm({
1746
+ "src/utils/logger.ts"() {
1747
+ "use strict";
1748
+ level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
1749
+ }
1750
+ });
1751
+
1752
+ // src/ml/client.ts
1753
+ function getMLClient() {
1754
+ return mlClient;
1755
+ }
1756
+ var log, mlClient;
1757
+ var init_client = __esm({
1758
+ "src/ml/client.ts"() {
1759
+ "use strict";
1760
+ init_logger();
1761
+ log = createLogger("ml-client");
1762
+ mlClient = null;
1763
+ }
1764
+ });
1765
+
1766
+ // src/ml/feature-engineer.ts
1767
+ async function buildFeatureVector(symbol) {
1768
+ const [ta, fundingResult, tickerResult, fgResult, klines] = await Promise.allSettled([
1769
+ analyzeTechnicals(symbol, "4h"),
1770
+ fetchFundingRate(symbol),
1771
+ fetchTickerPrice(symbol),
1772
+ fetchFearGreedIndex(1),
1773
+ fetchKlines(symbol, "4h", 100)
1774
+ ]);
1775
+ const indicators = ta.status === "fulfilled" ? ta.value.indicators : null;
1776
+ const funding = fundingResult.status === "fulfilled" ? fundingResult.value : null;
1777
+ const ticker = tickerResult.status === "fulfilled" ? tickerResult.value : null;
1778
+ const fg = fgResult.status === "fulfilled" ? fgResult.value : null;
1779
+ const candles = klines.status === "fulfilled" ? klines.value : [];
1780
+ let rsiSlope = 0;
1781
+ if (candles.length >= 17) {
1782
+ const closes = candles.map((k) => k.close);
1783
+ const recentRsi = calculateRSI(closes, 14);
1784
+ const olderCloses = closes.slice(0, -3);
1785
+ const olderRsi = calculateRSI(olderCloses, 14);
1786
+ if (recentRsi !== null && olderRsi !== null) {
1787
+ rsiSlope = recentRsi - olderRsi;
1788
+ }
1789
+ }
1790
+ let volumeRatio = 1;
1791
+ if (candles.length >= 21) {
1792
+ const currentVolume = candles[candles.length - 1].volume;
1793
+ const avgVolume = candles.slice(-21, -1).reduce((sum, k) => sum + k.volume, 0) / 20;
1794
+ volumeRatio = avgVolume > 0 ? currentVolume / avgVolume : 1;
1795
+ }
1796
+ const price = ticker?.price ?? candles[candles.length - 1]?.close ?? 0;
1797
+ const ema12 = indicators?.ema12 ?? 0;
1798
+ const ema26 = indicators?.ema26 ?? 0;
1799
+ const emaCrossoverPct = price > 0 ? (ema12 - ema26) / price * 100 : 0;
1800
+ const atr = indicators?.atr ?? 0;
1801
+ const atrPct = price > 0 ? atr / price * 100 : 0;
1802
+ return {
1803
+ rsi: indicators?.rsi ?? 50,
1804
+ macdHistogram: indicators?.macd?.histogram ?? 0,
1805
+ bollingerPercentB: indicators?.bollingerBands?.percentB ?? 0.5,
1806
+ ema12,
1807
+ ema26,
1808
+ atr,
1809
+ obv: indicators?.obv ?? 0,
1810
+ fundingRate: funding?.fundingRate ?? 0,
1811
+ fearGreed: fg?.current.value ?? 50,
1812
+ priceChange24h: ticker?.change24h ?? 0,
1813
+ rsiSlope,
1814
+ volumeRatio,
1815
+ emaCrossoverPct,
1816
+ atrPct,
1817
+ symbol: symbol.toUpperCase(),
1818
+ timestamp: Date.now()
1819
+ };
1820
+ }
1821
+ var init_feature_engineer = __esm({
1822
+ "src/ml/feature-engineer.ts"() {
1823
+ "use strict";
1824
+ init_technical_analysis();
1825
+ init_binance();
1826
+ init_fear_greed();
1827
+ init_indicators();
1828
+ }
1829
+ });
1830
+
1717
1831
  // src/core/trends/predictor.ts
1718
1832
  async function generatePrediction(symbol) {
1719
1833
  const reasoning = [];
@@ -1834,7 +1948,7 @@ async function generatePrediction(symbol) {
1834
1948
  const negativeCount = signalValues.filter((v) => v < 0).length;
1835
1949
  const agreement = Math.max(positiveCount, negativeCount) / Math.max(1, positiveCount + negativeCount);
1836
1950
  const confidence = Math.round(Math.min(95, completeness / 5 * agreement * 100));
1837
- return {
1951
+ const rulePrediction = {
1838
1952
  symbol: symbol.toUpperCase(),
1839
1953
  direction,
1840
1954
  confidence,
@@ -1844,6 +1958,34 @@ async function generatePrediction(symbol) {
1844
1958
  composite: Math.round(composite),
1845
1959
  disclaimer: "This is not financial advice. Predictions are based on historical data and AI analysis. Always do your own research."
1846
1960
  };
1961
+ const mlClient2 = getMLClient();
1962
+ if (mlClient2) {
1963
+ try {
1964
+ const features = await buildFeatureVector(symbol);
1965
+ const mlPred = await mlClient2.predict(features);
1966
+ if (mlPred) {
1967
+ return mergePredictions(rulePrediction, mlPred);
1968
+ }
1969
+ } catch {
1970
+ }
1971
+ }
1972
+ return rulePrediction;
1973
+ }
1974
+ function mergePredictions(rule, ml) {
1975
+ const mlComposite = ml.direction === "up" ? ml.probability * 100 : ml.direction === "down" ? -(ml.probability * 100) : 0;
1976
+ const mergedComposite = Math.round(rule.composite * 0.4 + mlComposite * 0.6);
1977
+ const mergedDirection = mergedComposite > 15 ? "up" : mergedComposite < -15 ? "down" : "sideways";
1978
+ const mergedConfidence = Math.round(Math.min(95, rule.confidence * 0.4 + ml.confidence * 0.6));
1979
+ return {
1980
+ ...rule,
1981
+ direction: mergedDirection,
1982
+ confidence: mergedConfidence,
1983
+ composite: mergedComposite,
1984
+ reasoning: [
1985
+ ...rule.reasoning,
1986
+ `ML (${ml.model}): ${ml.direction} with ${(ml.probability * 100).toFixed(1)}% probability (horizon: ${ml.horizon})`
1987
+ ]
1988
+ };
1847
1989
  }
1848
1990
  var WEIGHTS2;
1849
1991
  var init_predictor = __esm({
@@ -1853,6 +1995,8 @@ var init_predictor = __esm({
1853
1995
  init_sentiment();
1854
1996
  init_fear_greed();
1855
1997
  init_binance();
1998
+ init_client();
1999
+ init_feature_engineer();
1856
2000
  WEIGHTS2 = {
1857
2001
  technical: 40,
1858
2002
  sentiment: 20,
@@ -5233,7 +5377,7 @@ async function analyze(systemPrompt, userMessage, tools) {
5233
5377
  return p.analyze(systemPrompt, userMessage, tools, toolHandler);
5234
5378
  }
5235
5379
  var provider, config, toolHandler;
5236
- var init_client = __esm({
5380
+ var init_client2 = __esm({
5237
5381
  "src/ai/client.ts"() {
5238
5382
  "use strict";
5239
5383
  init_registry2();
@@ -5557,32 +5701,6 @@ var init_cache = __esm({
5557
5701
  }
5558
5702
  });
5559
5703
 
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
5704
  // src/core/agent/engine.ts
5587
5705
  var logger, AgentEngine;
5588
5706
  var init_engine = __esm({
@@ -6053,792 +6171,1320 @@ var init_agent = __esm({
6053
6171
  }
6054
6172
  });
6055
6173
 
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;
6174
+ // src/data/sqlite-store.ts
6175
+ function ensureAgentTables2() {
6176
+ const db2 = getDb();
6177
+ db2.exec(`
6178
+ CREATE TABLE IF NOT EXISTS agents (
6179
+ id TEXT PRIMARY KEY,
6180
+ name TEXT NOT NULL UNIQUE,
6181
+ strategy TEXT NOT NULL,
6182
+ pairs TEXT NOT NULL,
6183
+ interval_seconds INTEGER NOT NULL DEFAULT 60,
6184
+ created_at INTEGER NOT NULL,
6185
+ updated_at INTEGER NOT NULL
6186
+ )
6187
+ `);
6188
+ db2.exec(`
6189
+ CREATE TABLE IF NOT EXISTS agent_decisions (
6190
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6191
+ agent_id TEXT NOT NULL,
6192
+ symbol TEXT NOT NULL,
6193
+ action TEXT NOT NULL,
6194
+ confidence INTEGER NOT NULL,
6195
+ reasoning TEXT NOT NULL,
6196
+ signals TEXT NOT NULL,
6197
+ created_at INTEGER NOT NULL,
6198
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
6199
+ )
6200
+ `);
6201
+ }
6202
+ function rowToConfig(row) {
6203
+ return {
6204
+ id: row.id,
6205
+ name: row.name,
6206
+ strategy: row.strategy,
6207
+ pairs: JSON.parse(row.pairs),
6208
+ interval: row.interval_seconds,
6209
+ createdAt: row.created_at,
6210
+ updatedAt: row.updated_at
6211
+ };
6212
+ }
6213
+ var SqliteStore;
6214
+ var init_sqlite_store = __esm({
6215
+ "src/data/sqlite-store.ts"() {
6216
+ "use strict";
6217
+ init_cache();
6218
+ SqliteStore = class {
6219
+ // ---- Cache ---------------------------------------------------------------
6220
+ async getCached(key) {
6221
+ return getCached(key);
6128
6222
  }
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
- };
6223
+ async setCache(key, value, ttlSeconds) {
6224
+ setCache(key, value, ttlSeconds);
6166
6225
  }
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)
6226
+ // ---- Agents --------------------------------------------------------------
6227
+ async createAgent(config2) {
6228
+ ensureAgentTables2();
6229
+ getDb().prepare(
6230
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
6231
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
6232
+ ).run(
6233
+ config2.id,
6234
+ config2.name,
6235
+ config2.strategy,
6236
+ JSON.stringify(config2.pairs),
6237
+ config2.interval,
6238
+ config2.createdAt,
6239
+ config2.updatedAt
6237
6240
  );
6241
+ return config2;
6238
6242
  }
6239
- if (params["chain"]) {
6240
- const ch = String(params["chain"]).toLowerCase();
6241
- filtered = filtered.filter((r) => r.chains.some((c) => c.toLowerCase().includes(ch)));
6243
+ async listAgents() {
6244
+ ensureAgentTables2();
6245
+ const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
6246
+ return rows.map(rowToConfig);
6242
6247
  }
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}` };
6248
+ async getAgentById(id) {
6249
+ ensureAgentTables2();
6250
+ const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
6251
+ return row ? rowToConfig(row) : null;
6262
6252
  }
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;
6253
+ async getAgentByName(name) {
6254
+ ensureAgentTables2();
6255
+ const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
6256
+ return row ? rowToConfig(row) : null;
6307
6257
  }
6308
- if (oiResult.status === "fulfilled") {
6309
- result["openInterest"] = oiResult.value.openInterest;
6310
- result["openInterestNotional"] = oiResult.value.notionalValue;
6258
+ async deleteAgent(id) {
6259
+ ensureAgentTables2();
6260
+ const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
6261
+ getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
6262
+ return result.changes > 0;
6311
6263
  }
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();
6264
+ async logDecision(result) {
6265
+ ensureAgentTables2();
6266
+ getDb().prepare(
6267
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
6268
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
6269
+ ).run(
6270
+ result.agentId,
6271
+ result.symbol,
6272
+ result.decision.action,
6273
+ result.decision.confidence,
6274
+ JSON.stringify(result.decision.reasoning),
6275
+ JSON.stringify(result.signals),
6276
+ result.timestamp
6277
+ );
6278
+ }
6279
+ async getDecisions(agentId, limit) {
6280
+ ensureAgentTables2();
6281
+ const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
6282
+ return rows.map((r) => ({
6283
+ agentId: r.agent_id,
6284
+ symbol: r.symbol,
6285
+ timestamp: r.created_at,
6286
+ signals: JSON.parse(r.signals),
6287
+ decision: {
6288
+ action: r.action,
6289
+ confidence: r.confidence,
6290
+ reasoning: JSON.parse(r.reasoning)
6291
+ }
6292
+ }));
6293
+ }
6294
+ // ---- Time-series (no-op for SQLite) --------------------------------------
6295
+ async insertOHLCV(_records) {
6296
+ }
6297
+ async queryOHLCV(_symbol, _timeframe, _from, _to) {
6298
+ return [];
6299
+ }
6300
+ // ---- Predictions (no-op for SQLite) --------------------------------------
6301
+ async logPrediction(_prediction) {
6302
+ }
6303
+ async getPredictionAccuracy(_model, _days) {
6304
+ return {
6305
+ model: _model,
6306
+ totalPredictions: 0,
6307
+ correctPredictions: 0,
6308
+ accuracy: 0,
6309
+ byDirection: {
6310
+ up: { total: 0, correct: 0, accuracy: 0 },
6311
+ down: { total: 0, correct: 0, accuracy: 0 },
6312
+ sideways: { total: 0, correct: 0, accuracy: 0 }
6313
+ },
6314
+ period: `${_days}d`
6315
+ };
6316
+ }
6317
+ // ---- Lifecycle -----------------------------------------------------------
6318
+ async close() {
6319
+ }
6320
+ };
6429
6321
  }
6430
6322
  });
6431
6323
 
6432
- // src/ai/tools.ts
6433
- var VIZZOR_TOOLS;
6434
- var init_tools = __esm({
6435
- "src/ai/tools.ts"() {
6324
+ // src/data/postgres-store.ts
6325
+ var postgres_store_exports = {};
6326
+ __export(postgres_store_exports, {
6327
+ PostgresStore: () => PostgresStore
6328
+ });
6329
+ import pg from "pg";
6330
+ import { readFileSync as readFileSync2 } from "fs";
6331
+ import { resolve as resolve2, dirname } from "path";
6332
+ import { fileURLToPath } from "url";
6333
+ var __filename, __dirname, PostgresStore;
6334
+ var init_postgres_store = __esm({
6335
+ "src/data/postgres-store.ts"() {
6436
6336
  "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
- },
6595
- {
6596
- name: "get_raises",
6597
- 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.",
6598
- input_schema: {
6599
- type: "object",
6600
- properties: {
6601
- category: {
6602
- type: "string",
6603
- description: 'Filter by sector/category (e.g. "defi", "infrastructure", "gaming").'
6604
- },
6605
- chain: {
6606
- type: "string",
6607
- description: 'Filter by blockchain (e.g. "ethereum", "solana").'
6608
- }
6609
- },
6610
- required: []
6611
- }
6612
- },
6613
- {
6614
- name: "get_token_security",
6615
- description: "Check token security via GoPlus API. Returns honeypot detection, tax analysis, mint/pause/blacklist capabilities, holder stats, and overall risk level. No API key required.",
6616
- input_schema: {
6617
- type: "object",
6618
- properties: {
6619
- address: {
6620
- type: "string",
6621
- description: "The token contract address."
6622
- },
6623
- chain: {
6624
- type: "string",
6625
- description: 'The blockchain (e.g. "ethereum", "bsc", "polygon", "arbitrum", "base").'
6626
- }
6627
- },
6628
- required: ["address", "chain"]
6629
- }
6630
- },
6631
- {
6632
- name: "get_fear_greed",
6633
- description: "Get the LIVE Crypto Fear & Greed Index with 7-day history (updated daily). Values: 0-20 Extreme Fear, 21-40 Fear, 41-60 Neutral, 61-80 Greed, 81-100 Extreme Greed. Call for any market sentiment question.",
6634
- input_schema: {
6635
- type: "object",
6636
- properties: {},
6637
- required: []
6638
- }
6639
- },
6640
- {
6641
- name: "get_derivatives_data",
6642
- description: "Get LIVE derivatives data from Binance Futures: funding rate (updates every 8h), open interest, and mark price for a trading pair. Essential for current market positioning analysis.",
6643
- input_schema: {
6644
- type: "object",
6645
- properties: {
6646
- symbol: {
6647
- type: "string",
6648
- description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
6649
- }
6650
- },
6651
- required: ["symbol"]
6652
- }
6653
- },
6654
- {
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.",
6657
- input_schema: {
6658
- type: "object",
6659
- properties: {
6660
- symbol: {
6661
- type: "string",
6662
- 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
- }
6668
- },
6669
- required: ["symbol"]
6670
- }
6671
- },
6672
- {
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.",
6675
- input_schema: {
6676
- type: "object",
6677
- properties: {
6678
- symbol: {
6679
- type: "string",
6680
- description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
6681
- }
6682
- },
6683
- required: ["symbol"]
6684
- }
6685
- },
6686
- {
6687
- name: "create_agent",
6688
- description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
6689
- input_schema: {
6690
- type: "object",
6691
- properties: {
6692
- name: {
6693
- type: "string",
6694
- description: 'A unique name for the agent (e.g. "btc-momentum-bot").'
6695
- },
6696
- strategy: {
6697
- type: "string",
6698
- description: 'Trading strategy: "momentum" (RSI+MACD) or "trend-following" (EMA crossover).'
6699
- },
6700
- pairs: {
6701
- type: "string",
6702
- description: 'Comma-separated trading pairs (e.g. "BTC,ETH,SOL").'
6703
- },
6704
- interval: {
6705
- type: "number",
6706
- description: "Cycle interval in seconds. Defaults to 60."
6707
- }
6708
- },
6709
- required: ["name", "strategy", "pairs"]
6710
- }
6711
- },
6712
- {
6713
- name: "list_agents",
6714
- description: "List all created trading agents with their status, strategy, and monitored pairs.",
6715
- input_schema: {
6716
- type: "object",
6717
- properties: {},
6718
- required: []
6719
- }
6720
- },
6721
- {
6722
- name: "get_agent_status",
6723
- description: "Get detailed status of a trading agent including cycle count, last decision, and recent trade signals.",
6724
- input_schema: {
6725
- type: "object",
6726
- properties: {
6727
- name: {
6728
- type: "string",
6729
- description: "The agent name."
6730
- }
6731
- },
6732
- required: ["name"]
6733
- }
6337
+ __filename = fileURLToPath(import.meta.url);
6338
+ __dirname = dirname(__filename);
6339
+ PostgresStore = class {
6340
+ pool;
6341
+ initialized = false;
6342
+ constructor(connectionUrl) {
6343
+ this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
6734
6344
  }
6735
- ];
6736
- }
6737
- });
6738
-
6739
- // src/utils/message-split.ts
6740
- function splitMessage(text, maxLen) {
6741
- if (text.length <= maxLen) return [text];
6742
- const chunks = [];
6743
- let remaining = text;
6744
- while (remaining.length > 0) {
6745
- if (remaining.length <= maxLen) {
6746
- chunks.push(remaining);
6747
- break;
6748
- }
6749
- let splitAt = remaining.lastIndexOf("\n\n", maxLen);
6750
- if (splitAt <= 0) splitAt = remaining.lastIndexOf("\n", maxLen);
6751
- if (splitAt <= 0) splitAt = remaining.lastIndexOf(" ", maxLen);
6752
- if (splitAt <= 0) splitAt = maxLen;
6753
- chunks.push(remaining.slice(0, splitAt));
6754
- remaining = remaining.slice(splitAt).trimStart();
6755
- }
6756
- return chunks;
6757
- }
6758
- var init_message_split = __esm({
6759
- "src/utils/message-split.ts"() {
6760
- "use strict";
6761
- }
6762
- });
6763
-
6764
- // src/discord/middleware/rate-limit.ts
6765
- function checkRateLimit(userId) {
6766
- const now = Date.now();
6767
- const entry = userLimits.get(userId);
6768
- if (!entry || now >= entry.resetAt) {
6769
- userLimits.set(userId, { count: 1, resetAt: now + WINDOW_MS });
6770
- return { allowed: true };
6771
- }
6772
- if (entry.count >= MAX_REQUESTS) {
6773
- return { allowed: false };
6774
- }
6775
- entry.count++;
6776
- return { allowed: true };
6777
- }
6778
- function startRateLimitCleanup(intervalMs = 3e5) {
6779
- return setInterval(() => {
6780
- const now = Date.now();
6781
- for (const [userId, entry] of userLimits) {
6782
- if (now >= entry.resetAt) {
6783
- userLimits.delete(userId);
6345
+ async init() {
6346
+ if (this.initialized) return;
6347
+ const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
6348
+ const sql = readFileSync2(migrationPath, "utf-8");
6349
+ await this.pool.query(sql);
6350
+ this.initialized = true;
6784
6351
  }
6785
- }
6786
- }, intervalMs);
6787
- }
6788
- var userLimits, MAX_REQUESTS, WINDOW_MS;
6789
- var init_rate_limit = __esm({
6790
- "src/discord/middleware/rate-limit.ts"() {
6791
- "use strict";
6792
- userLimits = /* @__PURE__ */ new Map();
6793
- MAX_REQUESTS = 10;
6794
- WINDOW_MS = 6e4;
6795
- }
6796
- });
6797
-
6798
- // src/discord/commands/index.ts
6799
- import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
6800
- function registerSlashCommands() {
6801
- return [
6802
- // Core commands
6803
- new SlashCommandBuilder().setName("scan").setDescription("Analyze a crypto project").addStringOption(
6804
- (opt) => opt.setName("project").setDescription("Project name or contract address").setRequired(true)
6805
- ).addStringOption(
6806
- (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
6807
- ).toJSON(),
6808
- new SlashCommandBuilder().setName("trends").setDescription("Trending tokens + market data").toJSON(),
6809
- new SlashCommandBuilder().setName("track").setDescription("Analyze a wallet").addStringOption(
6810
- (opt) => opt.setName("wallet").setDescription("Wallet address").setRequired(true)
6811
- ).addStringOption(
6812
- (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
6813
- ).toJSON(),
6814
- new SlashCommandBuilder().setName("ico").setDescription("Upcoming ICOs & fundraising rounds").toJSON(),
6815
- new SlashCommandBuilder().setName("audit").setDescription("Audit a smart contract").addStringOption(
6816
- (opt) => opt.setName("contract").setDescription("Contract address").setRequired(true)
6817
- ).addStringOption(
6818
- (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
6819
- ).toJSON(),
6820
- new SlashCommandBuilder().setName("help").setDescription("Show all Vizzor commands").toJSON(),
6821
- // Quick commands
6822
- new SlashCommandBuilder().setName("price").setDescription("Quick price check").addStringOption(
6823
- (opt) => opt.setName("symbol").setDescription("Token symbol (e.g. BTC, ETH)").setRequired(true)
6824
- ).toJSON(),
6825
- new SlashCommandBuilder().setName("predict").setDescription("AI prediction with multi-signal analysis").addStringOption(
6826
- (opt) => opt.setName("symbol").setDescription("Token symbol (e.g. BTC, ETH)").setRequired(true)
6827
- ).toJSON(),
6828
- new SlashCommandBuilder().setName("wallet").setDescription("Check wallet balance").addStringOption(
6829
- (opt) => opt.setName("address").setDescription("Ethereum address").setRequired(true)
6830
- ).toJSON(),
6831
- // Agent commands
6832
- new SlashCommandBuilder().setName("agent_create").setDescription("Create a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).addStringOption(
6833
- (opt) => opt.setName("strategy").setDescription("Strategy (momentum, trend-following)").setRequired(false)
6834
- ).addStringOption(
6835
- (opt) => opt.setName("pairs").setDescription("Comma-separated pairs (e.g. BTC,ETH,SOL)").setRequired(false)
6836
- ).toJSON(),
6837
- new SlashCommandBuilder().setName("agent_list").setDescription("List all trading agents").toJSON(),
6838
- new SlashCommandBuilder().setName("agent_start").setDescription("Start a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON(),
6839
- new SlashCommandBuilder().setName("agent_stop").setDescription("Stop a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON(),
6840
- new SlashCommandBuilder().setName("agent_status").setDescription("View agent status & recent decisions").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON(),
6841
- new SlashCommandBuilder().setName("agent_delete").setDescription("Delete a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON()
6352
+ async query(text, params) {
6353
+ await this.init();
6354
+ return this.pool.query(text, params);
6355
+ }
6356
+ // ---- Cache ---------------------------------------------------------------
6357
+ async getCached(key) {
6358
+ const now = Math.floor(Date.now() / 1e3);
6359
+ const { rows } = await this.query(
6360
+ "SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
6361
+ [key, now]
6362
+ );
6363
+ if (rows.length === 0) return null;
6364
+ return rows[0].value;
6365
+ }
6366
+ async setCache(key, value, ttlSeconds) {
6367
+ const now = Math.floor(Date.now() / 1e3);
6368
+ const expiresAt = now + ttlSeconds;
6369
+ await this.query(
6370
+ `INSERT INTO cache (key, value, expires_at, created_at)
6371
+ VALUES ($1, $2, $3, $4)
6372
+ ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
6373
+ [key, JSON.stringify(value), expiresAt, now]
6374
+ );
6375
+ }
6376
+ // ---- Agents --------------------------------------------------------------
6377
+ async createAgent(config2) {
6378
+ await this.query(
6379
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
6380
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6381
+ [
6382
+ config2.id,
6383
+ config2.name,
6384
+ config2.strategy,
6385
+ JSON.stringify(config2.pairs),
6386
+ config2.interval,
6387
+ config2.createdAt,
6388
+ config2.updatedAt
6389
+ ]
6390
+ );
6391
+ return config2;
6392
+ }
6393
+ async listAgents() {
6394
+ const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
6395
+ return rows.map((r) => ({
6396
+ id: r.id,
6397
+ name: r.name,
6398
+ strategy: r.strategy,
6399
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6400
+ interval: r.interval_seconds,
6401
+ createdAt: Number(r.created_at),
6402
+ updatedAt: Number(r.updated_at)
6403
+ }));
6404
+ }
6405
+ async getAgentById(id) {
6406
+ const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
6407
+ if (rows.length === 0) return null;
6408
+ const r = rows[0];
6409
+ return {
6410
+ id: r.id,
6411
+ name: r.name,
6412
+ strategy: r.strategy,
6413
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6414
+ interval: r.interval_seconds,
6415
+ createdAt: Number(r.created_at),
6416
+ updatedAt: Number(r.updated_at)
6417
+ };
6418
+ }
6419
+ async getAgentByName(name) {
6420
+ const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
6421
+ if (rows.length === 0) return null;
6422
+ const r = rows[0];
6423
+ return {
6424
+ id: r.id,
6425
+ name: r.name,
6426
+ strategy: r.strategy,
6427
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6428
+ interval: r.interval_seconds,
6429
+ createdAt: Number(r.created_at),
6430
+ updatedAt: Number(r.updated_at)
6431
+ };
6432
+ }
6433
+ async deleteAgent(id) {
6434
+ const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
6435
+ return (result.rowCount ?? 0) > 0;
6436
+ }
6437
+ async logDecision(result) {
6438
+ await this.query(
6439
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
6440
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6441
+ [
6442
+ result.agentId,
6443
+ result.symbol,
6444
+ result.decision.action,
6445
+ result.decision.confidence,
6446
+ JSON.stringify(result.decision.reasoning),
6447
+ JSON.stringify(result.signals),
6448
+ result.timestamp
6449
+ ]
6450
+ );
6451
+ }
6452
+ async getDecisions(agentId, limit) {
6453
+ const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
6454
+ agentId,
6455
+ limit
6456
+ ]);
6457
+ return rows.map((r) => ({
6458
+ agentId: r.agent_id,
6459
+ symbol: r.symbol,
6460
+ timestamp: Number(r.created_at),
6461
+ signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
6462
+ decision: {
6463
+ action: r.action,
6464
+ confidence: r.confidence,
6465
+ reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
6466
+ }
6467
+ }));
6468
+ }
6469
+ // ---- Time-series ---------------------------------------------------------
6470
+ async insertOHLCV(records) {
6471
+ if (records.length === 0) return;
6472
+ const values = [];
6473
+ const placeholders = [];
6474
+ for (let i = 0; i < records.length; i++) {
6475
+ const r = records[i];
6476
+ const offset = i * 9;
6477
+ placeholders.push(
6478
+ `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
6479
+ );
6480
+ values.push(
6481
+ new Date(r.time).toISOString(),
6482
+ r.symbol,
6483
+ r.timeframe,
6484
+ r.open,
6485
+ r.high,
6486
+ r.low,
6487
+ r.close,
6488
+ r.volume,
6489
+ r.trades
6490
+ );
6491
+ }
6492
+ await this.query(
6493
+ `INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
6494
+ VALUES ${placeholders.join(", ")}
6495
+ ON CONFLICT (symbol, timeframe, time) DO UPDATE
6496
+ SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
6497
+ close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
6498
+ values
6499
+ );
6500
+ }
6501
+ async queryOHLCV(symbol, timeframe, from, to) {
6502
+ const { rows } = await this.query(
6503
+ `SELECT * FROM ohlcv
6504
+ WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
6505
+ ORDER BY time ASC`,
6506
+ [symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
6507
+ );
6508
+ return rows.map((r) => ({
6509
+ time: new Date(r.time).getTime(),
6510
+ symbol: r.symbol,
6511
+ timeframe: r.timeframe,
6512
+ open: r.open,
6513
+ high: r.high,
6514
+ low: r.low,
6515
+ close: r.close,
6516
+ volume: r.volume,
6517
+ trades: r.trades
6518
+ }));
6519
+ }
6520
+ // ---- Predictions ---------------------------------------------------------
6521
+ async logPrediction(prediction) {
6522
+ await this.query(
6523
+ `INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
6524
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6525
+ [
6526
+ prediction.symbol,
6527
+ prediction.model,
6528
+ prediction.direction,
6529
+ prediction.probability,
6530
+ prediction.horizon,
6531
+ JSON.stringify(prediction.features),
6532
+ new Date(prediction.predictedAt).toISOString()
6533
+ ]
6534
+ );
6535
+ }
6536
+ async getPredictionAccuracy(model, days) {
6537
+ const since = new Date(Date.now() - days * 864e5).toISOString();
6538
+ const { rows: totals } = await this.query(
6539
+ `SELECT direction,
6540
+ COUNT(*)::text AS total,
6541
+ COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
6542
+ FROM predictions
6543
+ WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
6544
+ GROUP BY direction`,
6545
+ [model, since]
6546
+ );
6547
+ const byDir = {
6548
+ up: { total: 0, correct: 0, accuracy: 0 },
6549
+ down: { total: 0, correct: 0, accuracy: 0 },
6550
+ sideways: { total: 0, correct: 0, accuracy: 0 }
6551
+ };
6552
+ let totalAll = 0;
6553
+ let correctAll = 0;
6554
+ for (const row of totals) {
6555
+ const t = parseInt(row.total, 10);
6556
+ const c = parseInt(row.correct, 10);
6557
+ totalAll += t;
6558
+ correctAll += c;
6559
+ const dir = row.direction;
6560
+ if (byDir[dir]) {
6561
+ byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
6562
+ }
6563
+ }
6564
+ return {
6565
+ model,
6566
+ totalPredictions: totalAll,
6567
+ correctPredictions: correctAll,
6568
+ accuracy: totalAll > 0 ? correctAll / totalAll : 0,
6569
+ byDirection: byDir,
6570
+ period: `${days}d`
6571
+ };
6572
+ }
6573
+ // ---- Lifecycle -----------------------------------------------------------
6574
+ async close() {
6575
+ await this.pool.end();
6576
+ }
6577
+ };
6578
+ }
6579
+ });
6580
+
6581
+ // src/data/store-factory.ts
6582
+ async function getStore(config2) {
6583
+ if (instance) return instance;
6584
+ if (config2.database?.type === "postgres" && config2.database.url) {
6585
+ const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
6586
+ instance = new PostgresStore2(config2.database.url);
6587
+ } else {
6588
+ instance = new SqliteStore();
6589
+ }
6590
+ return instance;
6591
+ }
6592
+ function getStoreInstance() {
6593
+ return instance;
6594
+ }
6595
+ var instance;
6596
+ var init_store_factory = __esm({
6597
+ "src/data/store-factory.ts"() {
6598
+ "use strict";
6599
+ init_sqlite_store();
6600
+ instance = null;
6601
+ }
6602
+ });
6603
+
6604
+ // src/ai/tool-handler.ts
6605
+ async function handleTool(name, input) {
6606
+ const params = input;
6607
+ switch (name) {
6608
+ case "get_token_info": {
6609
+ const address = String(params["address"] ?? "");
6610
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6611
+ const adapter = getAdapter(chain);
6612
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
6613
+ const info = await adapter.getTokenInfo(address);
6614
+ return {
6615
+ address: info.address,
6616
+ name: info.name,
6617
+ symbol: info.symbol,
6618
+ decimals: info.decimals,
6619
+ totalSupply: info.totalSupply.toString()
6620
+ };
6621
+ }
6622
+ case "analyze_wallet": {
6623
+ const address = String(params["address"] ?? "");
6624
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6625
+ const adapter = getAdapter(chain);
6626
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
6627
+ const analysis = await analyzeWallet(address, adapter);
6628
+ return {
6629
+ address: analysis.address,
6630
+ chain: analysis.chain,
6631
+ balance: analysis.balance.toString(),
6632
+ transactionCount: analysis.transactionCount,
6633
+ riskLevel: analysis.riskLevel,
6634
+ patterns: analysis.patterns
6635
+ };
6636
+ }
6637
+ case "check_rug_indicators": {
6638
+ const address = String(params["address"] ?? "");
6639
+ const chain = String(params["chain"] ?? DEFAULT_CHAIN);
6640
+ const adapter = getAdapter(chain);
6641
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
6642
+ const indicators = await detectRugIndicators(address, adapter);
6643
+ return {
6644
+ isHoneypot: indicators.isHoneypot,
6645
+ hasLiquidityLock: indicators.hasLiquidityLock,
6646
+ ownerCanMint: indicators.ownerCanMint,
6647
+ ownerCanPause: indicators.ownerCanPause,
6648
+ hasBlacklist: indicators.hasBlacklist,
6649
+ highSellTax: indicators.highSellTax,
6650
+ riskScore: indicators.riskScore,
6651
+ details: indicators.details
6652
+ };
6653
+ }
6654
+ case "get_market_data": {
6655
+ const symbol = String(params["symbol"] ?? "");
6656
+ try {
6657
+ const binance = await fetchTickerPrice(symbol);
6658
+ const gecko = await fetchMarketData(symbol).catch(() => null);
6659
+ return {
6660
+ symbol: binance.symbol,
6661
+ name: gecko?.name ?? binance.symbol,
6662
+ price: binance.price,
6663
+ priceChange24h: binance.change24h,
6664
+ priceChange7d: gecko?.priceChange7d ?? null,
6665
+ volume24h: gecko?.volume24h ?? null,
6666
+ marketCap: gecko?.marketCap ?? null,
6667
+ rank: gecko?.rank ?? null,
6668
+ source: "binance+coingecko"
6669
+ };
6670
+ } catch {
6671
+ const data = await fetchMarketData(symbol);
6672
+ if (!data) {
6673
+ return { error: `No market data found for "${symbol}"` };
6674
+ }
6675
+ return data;
6676
+ }
6677
+ }
6678
+ case "search_upcoming_icos": {
6679
+ const category = params["category"] ? String(params["category"]) : void 0;
6680
+ const chain = params["chain"] ? String(params["chain"]) : void 0;
6681
+ const roundType = params["roundType"] ? String(params["roundType"]) : void 0;
6682
+ const projects = category || chain || roundType ? await searchICOs(void 0, category, chain, roundType) : await fetchUpcomingICOs();
6683
+ return {
6684
+ projects: projects.map((p) => ({
6685
+ name: p.name,
6686
+ category: p.category,
6687
+ chain: p.chain,
6688
+ roundType: p.roundType,
6689
+ raisedAmount: p.raisedAmount,
6690
+ valuation: p.valuation,
6691
+ investors: p.investors.slice(0, 5),
6692
+ startDate: p.startDate,
6693
+ description: p.description,
6694
+ website: p.website
6695
+ }))
6696
+ };
6697
+ }
6698
+ case "get_funding_history": {
6699
+ const fundingName = String(params["name"] ?? "");
6700
+ const type = String(params["type"] ?? "project");
6701
+ if (type === "investor") {
6702
+ const portfolio = await getInvestorPortfolio(fundingName);
6703
+ return {
6704
+ investor: fundingName,
6705
+ investments: portfolio.map((p) => ({
6706
+ name: p.name,
6707
+ round: p.roundType,
6708
+ amount: p.raisedAmount,
6709
+ chain: p.chain,
6710
+ category: p.category,
6711
+ date: p.startDate
6712
+ }))
6713
+ };
6714
+ }
6715
+ const history = await getProjectFundingHistory(fundingName);
6716
+ return {
6717
+ project: history.name,
6718
+ rounds: history.rounds.map((r) => ({
6719
+ round: r.roundType,
6720
+ amount: r.raisedAmount,
6721
+ valuation: r.valuation,
6722
+ investors: r.investors.slice(0, 5),
6723
+ date: r.startDate,
6724
+ previousRounds: r.previousRounds
6725
+ }))
6726
+ };
6727
+ }
6728
+ case "search_token_dex": {
6729
+ const query = String(params["query"] ?? "");
6730
+ const pairs = await fetchTokenFromDex(query);
6731
+ return {
6732
+ results: pairs.slice(0, 5).map((p) => ({
6733
+ name: p.baseToken.name,
6734
+ symbol: p.baseToken.symbol,
6735
+ chain: p.chainId,
6736
+ dex: p.dexId,
6737
+ priceUsd: p.priceUsd,
6738
+ volume24h: p.volume?.h24 ?? 0,
6739
+ liquidity: p.liquidity?.usd ?? 0,
6740
+ priceChange24h: p.priceChange?.h24 ?? 0,
6741
+ marketCap: p.marketCap ?? p.fdv ?? null,
6742
+ buys24h: p.txns?.h24?.buys ?? 0,
6743
+ sells24h: p.txns?.h24?.sells ?? 0,
6744
+ pairAddress: p.pairAddress,
6745
+ url: p.url
6746
+ }))
6747
+ };
6748
+ }
6749
+ case "get_trending": {
6750
+ const trending = await fetchTrendingTokens();
6751
+ return {
6752
+ trending: trending.slice(0, 10).map((t) => ({
6753
+ name: t.name,
6754
+ symbol: t.symbol,
6755
+ chain: t.chain,
6756
+ priceUsd: t.priceUsd,
6757
+ priceChange24h: t.priceChange24h,
6758
+ volume24h: t.volume24h,
6759
+ marketCap: t.marketCap,
6760
+ source: t.source,
6761
+ url: t.url
6762
+ }))
6763
+ };
6764
+ }
6765
+ case "get_crypto_news": {
6766
+ const symbol = params["symbol"] ? String(params["symbol"]) : void 0;
6767
+ const news = await fetchCryptoNews(symbol, getConfig().cryptopanicApiKey);
6768
+ return {
6769
+ news: news.slice(0, 10).map((n) => ({
6770
+ title: n.title,
6771
+ sentiment: n.sentiment,
6772
+ source: n.source.title,
6773
+ publishedAt: n.publishedAt,
6774
+ url: n.url
6775
+ }))
6776
+ };
6777
+ }
6778
+ case "get_raises": {
6779
+ const raises = await fetchRecentRaises(30);
6780
+ let filtered = raises;
6781
+ if (params["category"]) {
6782
+ const cat = String(params["category"]).toLowerCase();
6783
+ filtered = filtered.filter(
6784
+ (r) => r.category?.toLowerCase().includes(cat) || r.sector?.toLowerCase().includes(cat)
6785
+ );
6786
+ }
6787
+ if (params["chain"]) {
6788
+ const ch = String(params["chain"]).toLowerCase();
6789
+ filtered = filtered.filter((r) => r.chains.some((c) => c.toLowerCase().includes(ch)));
6790
+ }
6791
+ return {
6792
+ raises: filtered.slice(0, 10).map((r) => ({
6793
+ name: r.name,
6794
+ round: r.round,
6795
+ amount: r.amount,
6796
+ chains: r.chains,
6797
+ sector: r.sector,
6798
+ category: r.category,
6799
+ leadInvestors: r.leadInvestors,
6800
+ date: new Date(r.date * 1e3).toISOString().split("T")[0]
6801
+ }))
6802
+ };
6803
+ }
6804
+ case "get_token_security": {
6805
+ const address = String(params["address"] ?? "");
6806
+ const chain = String(params["chain"] ?? "ethereum");
6807
+ const security = await checkTokenSecurity(address, chain);
6808
+ if (!security) {
6809
+ return { error: `No security data for ${address} on ${chain}` };
6810
+ }
6811
+ return {
6812
+ contractAddress: security.contractAddress,
6813
+ chain: security.chain,
6814
+ riskLevel: security.riskLevel,
6815
+ isHoneypot: security.isHoneypot,
6816
+ isMintable: security.isMintable,
6817
+ buyTax: security.buyTax,
6818
+ sellTax: security.sellTax,
6819
+ isOpenSource: security.isOpenSource,
6820
+ isProxy: security.isProxy,
6821
+ hiddenOwner: security.hiddenOwner,
6822
+ cannotBuy: security.cannotBuy,
6823
+ cannotSellAll: security.cannotSellAll,
6824
+ isBlacklisted: security.isBlacklisted,
6825
+ holderCount: security.holderCount,
6826
+ lpHolderCount: security.lpHolderCount,
6827
+ creatorPercent: security.creatorPercent,
6828
+ ownerPercent: security.ownerPercent,
6829
+ trustList: security.trustList
6830
+ };
6831
+ }
6832
+ case "get_fear_greed": {
6833
+ const data = await fetchFearGreedIndex(7);
6834
+ return {
6835
+ current: { value: data.current.value, classification: data.current.classification },
6836
+ previous: data.previous ? { value: data.previous.value, classification: data.previous.classification } : null,
6837
+ history: data.history.map((h) => ({
6838
+ value: h.value,
6839
+ classification: h.classification,
6840
+ date: new Date(h.timestamp * 1e3).toISOString().split("T")[0]
6841
+ }))
6842
+ };
6843
+ }
6844
+ case "get_derivatives_data": {
6845
+ const symbol = String(params["symbol"] ?? "BTC");
6846
+ const [fundingResult, oiResult] = await Promise.allSettled([
6847
+ fetchFundingRate(symbol),
6848
+ fetchOpenInterest(symbol)
6849
+ ]);
6850
+ const result = { symbol: symbol.toUpperCase() };
6851
+ if (fundingResult.status === "fulfilled") {
6852
+ result["fundingRate"] = fundingResult.value.fundingRate;
6853
+ result["fundingRatePct"] = `${(fundingResult.value.fundingRate * 100).toFixed(4)}%`;
6854
+ result["markPrice"] = fundingResult.value.markPrice;
6855
+ }
6856
+ if (oiResult.status === "fulfilled") {
6857
+ result["openInterest"] = oiResult.value.openInterest;
6858
+ result["openInterestNotional"] = oiResult.value.notionalValue;
6859
+ }
6860
+ return result;
6861
+ }
6862
+ case "get_technical_analysis": {
6863
+ const symbol = String(params["symbol"] ?? "BTC");
6864
+ const timeframe = String(params["timeframe"] ?? "4h");
6865
+ const ta = await analyzeTechnicals(symbol, timeframe);
6866
+ return {
6867
+ symbol: ta.symbol,
6868
+ timeframe: ta.timeframe,
6869
+ composite: ta.composite,
6870
+ signals: ta.signals.map((s) => ({
6871
+ name: s.name,
6872
+ signal: s.signal,
6873
+ strength: s.strength,
6874
+ description: s.description
6875
+ })),
6876
+ indicators: {
6877
+ rsi: ta.indicators.rsi ? Math.round(ta.indicators.rsi * 100) / 100 : null,
6878
+ macd: ta.indicators.macd,
6879
+ bollingerBands: ta.indicators.bollingerBands,
6880
+ ema12: ta.indicators.ema12,
6881
+ ema26: ta.indicators.ema26,
6882
+ atr: ta.indicators.atr
6883
+ }
6884
+ };
6885
+ }
6886
+ case "get_prediction": {
6887
+ const symbol = String(params["symbol"] ?? "BTC");
6888
+ const prediction = await generatePrediction(symbol);
6889
+ return {
6890
+ symbol: prediction.symbol,
6891
+ direction: prediction.direction,
6892
+ confidence: prediction.confidence,
6893
+ composite: prediction.composite,
6894
+ timeframe: prediction.timeframe,
6895
+ signals: prediction.signals,
6896
+ reasoning: prediction.reasoning,
6897
+ disclaimer: prediction.disclaimer
6898
+ };
6899
+ }
6900
+ case "get_ml_prediction": {
6901
+ const symbol = String(params["symbol"] ?? "BTC");
6902
+ const mlClient2 = getMLClient();
6903
+ if (!mlClient2) {
6904
+ const prediction = await generatePrediction(symbol);
6905
+ return {
6906
+ ...prediction,
6907
+ mlAvailable: false,
6908
+ note: "ML sidecar not configured; using rule-based prediction"
6909
+ };
6910
+ }
6911
+ const features = await buildFeatureVector(symbol);
6912
+ const mlPred = await mlClient2.predict(features);
6913
+ if (!mlPred) {
6914
+ const prediction = await generatePrediction(symbol);
6915
+ return {
6916
+ ...prediction,
6917
+ mlAvailable: false,
6918
+ note: "ML sidecar unavailable; using rule-based prediction"
6919
+ };
6920
+ }
6921
+ return {
6922
+ symbol: mlPred.symbol,
6923
+ direction: mlPred.direction,
6924
+ probability: mlPred.probability,
6925
+ confidence: mlPred.confidence,
6926
+ model: mlPred.model,
6927
+ horizon: mlPred.horizon,
6928
+ mlAvailable: true,
6929
+ features: {
6930
+ rsi: features.rsi,
6931
+ macdHistogram: features.macdHistogram,
6932
+ bollingerPercentB: features.bollingerPercentB,
6933
+ fundingRate: features.fundingRate,
6934
+ fearGreed: features.fearGreed,
6935
+ rsiSlope: features.rsiSlope,
6936
+ volumeRatio: features.volumeRatio,
6937
+ emaCrossoverPct: features.emaCrossoverPct,
6938
+ atrPct: features.atrPct
6939
+ }
6940
+ };
6941
+ }
6942
+ case "get_model_accuracy": {
6943
+ const model = String(params["model"] ?? "lstm-predictor");
6944
+ const days = params["days"] ? Number(params["days"]) : 30;
6945
+ const store = getStoreInstance();
6946
+ if (!store) {
6947
+ return { error: "DataStore not initialized. ML accuracy requires PostgreSQL backend." };
6948
+ }
6949
+ const accuracy = await store.getPredictionAccuracy(model, days);
6950
+ return {
6951
+ model: accuracy.model,
6952
+ period: accuracy.period,
6953
+ totalPredictions: accuracy.totalPredictions,
6954
+ correctPredictions: accuracy.correctPredictions,
6955
+ accuracy: `${(accuracy.accuracy * 100).toFixed(1)}%`,
6956
+ byDirection: {
6957
+ up: `${(accuracy.byDirection.up.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.up.correct}/${accuracy.byDirection.up.total})`,
6958
+ down: `${(accuracy.byDirection.down.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.down.correct}/${accuracy.byDirection.down.total})`,
6959
+ sideways: `${(accuracy.byDirection.sideways.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.sideways.correct}/${accuracy.byDirection.sideways.total})`
6960
+ }
6961
+ };
6962
+ }
6963
+ case "create_agent": {
6964
+ const agentName = String(params["name"] ?? "");
6965
+ const strategy = String(params["strategy"] ?? "momentum");
6966
+ const pairsRaw = String(params["pairs"] ?? "BTC,ETH");
6967
+ const interval = params["interval"] ? Number(params["interval"]) : 60;
6968
+ const agentPairs = pairsRaw.split(",").map((p) => p.trim().toUpperCase());
6969
+ const agent = createAgent(agentName, strategy, agentPairs, interval);
6970
+ return {
6971
+ id: agent.id,
6972
+ name: agent.name,
6973
+ strategy: agent.strategy,
6974
+ pairs: agent.pairs,
6975
+ interval: agent.interval,
6976
+ message: `Agent "${agent.name}" created. Use /agent start ${agent.name} to activate.`
6977
+ };
6978
+ }
6979
+ case "list_agents": {
6980
+ const agents = listAgents();
6981
+ return {
6982
+ agents: agents.map((a) => {
6983
+ const status = getAgentStatus(a.id);
6984
+ return {
6985
+ name: a.name,
6986
+ strategy: a.strategy,
6987
+ pairs: a.pairs,
6988
+ interval: a.interval,
6989
+ status: status?.status ?? "idle",
6990
+ cycleCount: status?.cycleCount ?? 0
6991
+ };
6992
+ })
6993
+ };
6994
+ }
6995
+ case "get_agent_status": {
6996
+ const agentName = String(params["name"] ?? "");
6997
+ const agent = getAgentByName(agentName);
6998
+ if (!agent) return { error: `Agent "${agentName}" not found` };
6999
+ const state = getAgentStatus(agent.id);
7000
+ if (!state) return { error: `Agent "${agentName}" not found` };
7001
+ const decisions = getRecentDecisions(agent.id, 5);
7002
+ return {
7003
+ name: state.config.name,
7004
+ status: state.status,
7005
+ strategy: state.config.strategy,
7006
+ pairs: state.config.pairs,
7007
+ cycleCount: state.cycleCount,
7008
+ error: state.error,
7009
+ recentDecisions: decisions.map((d) => ({
7010
+ symbol: d.symbol,
7011
+ action: d.decision.action,
7012
+ confidence: d.decision.confidence,
7013
+ reasoning: d.decision.reasoning,
7014
+ timestamp: new Date(d.timestamp).toISOString()
7015
+ }))
7016
+ };
7017
+ }
7018
+ default:
7019
+ return { error: `Unknown tool: ${name}` };
7020
+ }
7021
+ }
7022
+ var init_tool_handler = __esm({
7023
+ "src/ai/tool-handler.ts"() {
7024
+ "use strict";
7025
+ init_registry();
7026
+ init_loader();
7027
+ init_constants();
7028
+ init_wallet_analyzer();
7029
+ init_rug_detector();
7030
+ init_market();
7031
+ init_ico_tracker();
7032
+ init_cryptopanic();
7033
+ init_defillama();
7034
+ init_binance();
7035
+ init_goplus();
7036
+ init_fear_greed();
7037
+ init_technical_analysis();
7038
+ init_predictor();
7039
+ init_agent();
7040
+ init_client();
7041
+ init_feature_engineer();
7042
+ init_store_factory();
7043
+ }
7044
+ });
7045
+
7046
+ // src/ai/tools.ts
7047
+ var VIZZOR_TOOLS;
7048
+ var init_tools = __esm({
7049
+ "src/ai/tools.ts"() {
7050
+ "use strict";
7051
+ VIZZOR_TOOLS = [
7052
+ {
7053
+ name: "get_token_info",
7054
+ description: "Get on-chain token information including name, symbol, decimals, total supply, and top holders for a given contract address and chain.",
7055
+ input_schema: {
7056
+ type: "object",
7057
+ properties: {
7058
+ address: {
7059
+ type: "string",
7060
+ description: "The token contract address (e.g. 0x...)."
7061
+ },
7062
+ chain: {
7063
+ type: "string",
7064
+ description: 'The blockchain to query (e.g. "ethereum", "bsc", "polygon", "arbitrum").'
7065
+ }
7066
+ },
7067
+ required: ["address", "chain"]
7068
+ }
7069
+ },
7070
+ {
7071
+ name: "analyze_wallet",
7072
+ description: "Analyze a wallet address for transaction patterns, token holdings, DeFi interactions, and behavioral signals such as accumulation or distribution phases.",
7073
+ input_schema: {
7074
+ type: "object",
7075
+ properties: {
7076
+ address: {
7077
+ type: "string",
7078
+ description: "The wallet address to analyze."
7079
+ },
7080
+ chain: {
7081
+ type: "string",
7082
+ description: 'The blockchain the wallet resides on (e.g. "ethereum", "bsc").'
7083
+ },
7084
+ depth: {
7085
+ type: "number",
7086
+ description: "How many recent transactions to inspect. Defaults to 100."
7087
+ }
7088
+ },
7089
+ required: ["address", "chain"]
7090
+ }
7091
+ },
7092
+ {
7093
+ name: "check_rug_indicators",
7094
+ description: "Check a token for common rug pull indicators including honeypot detection, liquidity locks, ownership status, hidden mints, and holder concentration.",
7095
+ input_schema: {
7096
+ type: "object",
7097
+ properties: {
7098
+ address: {
7099
+ type: "string",
7100
+ description: "The token contract address to check."
7101
+ },
7102
+ chain: {
7103
+ type: "string",
7104
+ description: "The blockchain the token is deployed on."
7105
+ }
7106
+ },
7107
+ required: ["address", "chain"]
7108
+ }
7109
+ },
7110
+ {
7111
+ name: "get_market_data",
7112
+ 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.",
7113
+ input_schema: {
7114
+ type: "object",
7115
+ properties: {
7116
+ symbol: {
7117
+ type: "string",
7118
+ description: 'The token ticker symbol (e.g. "ETH", "BTC", "UNI").'
7119
+ },
7120
+ currency: {
7121
+ type: "string",
7122
+ description: 'The fiat currency for price quotes. Defaults to "usd".'
7123
+ }
7124
+ },
7125
+ required: ["symbol"]
7126
+ }
7127
+ },
7128
+ {
7129
+ name: "search_upcoming_icos",
7130
+ 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.",
7131
+ input_schema: {
7132
+ type: "object",
7133
+ properties: {
7134
+ category: {
7135
+ type: "string",
7136
+ description: 'Filter by project category (e.g. "defi", "gaming", "infrastructure", "nft", "ai").'
7137
+ },
7138
+ chain: {
7139
+ type: "string",
7140
+ description: 'Filter by blockchain (e.g. "ethereum", "solana", "bsc").'
7141
+ },
7142
+ roundType: {
7143
+ type: "string",
7144
+ description: 'Filter by funding round type (e.g. "Seed", "Pre-Seed", "Series A", "Series B", "Token Launch").'
7145
+ },
7146
+ limit: {
7147
+ type: "number",
7148
+ description: "Maximum number of results to return. Defaults to 10."
7149
+ }
7150
+ },
7151
+ required: []
7152
+ }
7153
+ },
7154
+ {
7155
+ name: "get_funding_history",
7156
+ 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.",
7157
+ input_schema: {
7158
+ type: "object",
7159
+ properties: {
7160
+ name: {
7161
+ type: "string",
7162
+ description: "Project name or investor name to look up."
7163
+ },
7164
+ type: {
7165
+ type: "string",
7166
+ description: 'Type of lookup: "project" for project funding history, "investor" for investor portfolio. Defaults to "project".'
7167
+ }
7168
+ },
7169
+ required: ["name"]
7170
+ }
7171
+ },
7172
+ {
7173
+ name: "search_token_dex",
7174
+ 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.",
7175
+ input_schema: {
7176
+ type: "object",
7177
+ properties: {
7178
+ query: {
7179
+ type: "string",
7180
+ description: "Token name, symbol, or contract address to search for."
7181
+ }
7182
+ },
7183
+ required: ["query"]
7184
+ }
7185
+ },
7186
+ {
7187
+ name: "get_trending",
7188
+ 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.",
7189
+ input_schema: {
7190
+ type: "object",
7191
+ properties: {},
7192
+ required: []
7193
+ }
7194
+ },
7195
+ {
7196
+ name: "get_crypto_news",
7197
+ 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.",
7198
+ input_schema: {
7199
+ type: "object",
7200
+ properties: {
7201
+ symbol: {
7202
+ type: "string",
7203
+ description: 'Token symbol to filter news for (e.g. "BTC", "ETH", "SOL"). Omit for general crypto news.'
7204
+ }
7205
+ },
7206
+ required: []
7207
+ }
7208
+ },
7209
+ {
7210
+ name: "get_raises",
7211
+ 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.",
7212
+ input_schema: {
7213
+ type: "object",
7214
+ properties: {
7215
+ category: {
7216
+ type: "string",
7217
+ description: 'Filter by sector/category (e.g. "defi", "infrastructure", "gaming").'
7218
+ },
7219
+ chain: {
7220
+ type: "string",
7221
+ description: 'Filter by blockchain (e.g. "ethereum", "solana").'
7222
+ }
7223
+ },
7224
+ required: []
7225
+ }
7226
+ },
7227
+ {
7228
+ name: "get_token_security",
7229
+ description: "Check token security via GoPlus API. Returns honeypot detection, tax analysis, mint/pause/blacklist capabilities, holder stats, and overall risk level. No API key required.",
7230
+ input_schema: {
7231
+ type: "object",
7232
+ properties: {
7233
+ address: {
7234
+ type: "string",
7235
+ description: "The token contract address."
7236
+ },
7237
+ chain: {
7238
+ type: "string",
7239
+ description: 'The blockchain (e.g. "ethereum", "bsc", "polygon", "arbitrum", "base").'
7240
+ }
7241
+ },
7242
+ required: ["address", "chain"]
7243
+ }
7244
+ },
7245
+ {
7246
+ name: "get_fear_greed",
7247
+ description: "Get the LIVE Crypto Fear & Greed Index with 7-day history (updated daily). Values: 0-20 Extreme Fear, 21-40 Fear, 41-60 Neutral, 61-80 Greed, 81-100 Extreme Greed. Call for any market sentiment question.",
7248
+ input_schema: {
7249
+ type: "object",
7250
+ properties: {},
7251
+ required: []
7252
+ }
7253
+ },
7254
+ {
7255
+ name: "get_derivatives_data",
7256
+ description: "Get LIVE derivatives data from Binance Futures: funding rate (updates every 8h), open interest, and mark price for a trading pair. Essential for current market positioning analysis.",
7257
+ input_schema: {
7258
+ type: "object",
7259
+ properties: {
7260
+ symbol: {
7261
+ type: "string",
7262
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7263
+ }
7264
+ },
7265
+ required: ["symbol"]
7266
+ }
7267
+ },
7268
+ {
7269
+ name: "get_technical_analysis",
7270
+ 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.",
7271
+ input_schema: {
7272
+ type: "object",
7273
+ properties: {
7274
+ symbol: {
7275
+ type: "string",
7276
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7277
+ },
7278
+ timeframe: {
7279
+ type: "string",
7280
+ description: 'Kline interval: "1h", "4h", "1d". Defaults to "4h".'
7281
+ }
7282
+ },
7283
+ required: ["symbol"]
7284
+ }
7285
+ },
7286
+ {
7287
+ name: "get_prediction",
7288
+ 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.",
7289
+ input_schema: {
7290
+ type: "object",
7291
+ properties: {
7292
+ symbol: {
7293
+ type: "string",
7294
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7295
+ }
7296
+ },
7297
+ required: ["symbol"]
7298
+ }
7299
+ },
7300
+ {
7301
+ name: "get_ml_prediction",
7302
+ 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.",
7303
+ input_schema: {
7304
+ type: "object",
7305
+ properties: {
7306
+ symbol: {
7307
+ type: "string",
7308
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7309
+ }
7310
+ },
7311
+ required: ["symbol"]
7312
+ }
7313
+ },
7314
+ {
7315
+ name: "get_model_accuracy",
7316
+ description: "Get historical accuracy metrics for ML prediction models. Shows total predictions, accuracy percentage, and breakdown by direction (up/down/sideways).",
7317
+ input_schema: {
7318
+ type: "object",
7319
+ properties: {
7320
+ model: {
7321
+ type: "string",
7322
+ description: 'Model name (e.g. "lstm-predictor", "signal-classifier"). Defaults to "lstm-predictor".'
7323
+ },
7324
+ days: {
7325
+ type: "number",
7326
+ description: "Number of days to look back for accuracy stats. Defaults to 30."
7327
+ }
7328
+ },
7329
+ required: []
7330
+ }
7331
+ },
7332
+ {
7333
+ name: "create_agent",
7334
+ description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
7335
+ input_schema: {
7336
+ type: "object",
7337
+ properties: {
7338
+ name: {
7339
+ type: "string",
7340
+ description: 'A unique name for the agent (e.g. "btc-momentum-bot").'
7341
+ },
7342
+ strategy: {
7343
+ type: "string",
7344
+ description: 'Trading strategy: "momentum" (RSI+MACD) or "trend-following" (EMA crossover).'
7345
+ },
7346
+ pairs: {
7347
+ type: "string",
7348
+ description: 'Comma-separated trading pairs (e.g. "BTC,ETH,SOL").'
7349
+ },
7350
+ interval: {
7351
+ type: "number",
7352
+ description: "Cycle interval in seconds. Defaults to 60."
7353
+ }
7354
+ },
7355
+ required: ["name", "strategy", "pairs"]
7356
+ }
7357
+ },
7358
+ {
7359
+ name: "list_agents",
7360
+ description: "List all created trading agents with their status, strategy, and monitored pairs.",
7361
+ input_schema: {
7362
+ type: "object",
7363
+ properties: {},
7364
+ required: []
7365
+ }
7366
+ },
7367
+ {
7368
+ name: "get_agent_status",
7369
+ description: "Get detailed status of a trading agent including cycle count, last decision, and recent trade signals.",
7370
+ input_schema: {
7371
+ type: "object",
7372
+ properties: {
7373
+ name: {
7374
+ type: "string",
7375
+ description: "The agent name."
7376
+ }
7377
+ },
7378
+ required: ["name"]
7379
+ }
7380
+ }
7381
+ ];
7382
+ }
7383
+ });
7384
+
7385
+ // src/utils/message-split.ts
7386
+ function splitMessage(text, maxLen) {
7387
+ if (text.length <= maxLen) return [text];
7388
+ const chunks = [];
7389
+ let remaining = text;
7390
+ while (remaining.length > 0) {
7391
+ if (remaining.length <= maxLen) {
7392
+ chunks.push(remaining);
7393
+ break;
7394
+ }
7395
+ let splitAt = remaining.lastIndexOf("\n\n", maxLen);
7396
+ if (splitAt <= 0) splitAt = remaining.lastIndexOf("\n", maxLen);
7397
+ if (splitAt <= 0) splitAt = remaining.lastIndexOf(" ", maxLen);
7398
+ if (splitAt <= 0) splitAt = maxLen;
7399
+ chunks.push(remaining.slice(0, splitAt));
7400
+ remaining = remaining.slice(splitAt).trimStart();
7401
+ }
7402
+ return chunks;
7403
+ }
7404
+ var init_message_split = __esm({
7405
+ "src/utils/message-split.ts"() {
7406
+ "use strict";
7407
+ }
7408
+ });
7409
+
7410
+ // src/discord/middleware/rate-limit.ts
7411
+ function checkRateLimit(userId) {
7412
+ const now = Date.now();
7413
+ const entry = userLimits.get(userId);
7414
+ if (!entry || now >= entry.resetAt) {
7415
+ userLimits.set(userId, { count: 1, resetAt: now + WINDOW_MS });
7416
+ return { allowed: true };
7417
+ }
7418
+ if (entry.count >= MAX_REQUESTS) {
7419
+ return { allowed: false };
7420
+ }
7421
+ entry.count++;
7422
+ return { allowed: true };
7423
+ }
7424
+ function startRateLimitCleanup(intervalMs = 3e5) {
7425
+ return setInterval(() => {
7426
+ const now = Date.now();
7427
+ for (const [userId, entry] of userLimits) {
7428
+ if (now >= entry.resetAt) {
7429
+ userLimits.delete(userId);
7430
+ }
7431
+ }
7432
+ }, intervalMs);
7433
+ }
7434
+ var userLimits, MAX_REQUESTS, WINDOW_MS;
7435
+ var init_rate_limit = __esm({
7436
+ "src/discord/middleware/rate-limit.ts"() {
7437
+ "use strict";
7438
+ userLimits = /* @__PURE__ */ new Map();
7439
+ MAX_REQUESTS = 10;
7440
+ WINDOW_MS = 6e4;
7441
+ }
7442
+ });
7443
+
7444
+ // src/discord/commands/index.ts
7445
+ import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
7446
+ function registerSlashCommands() {
7447
+ return [
7448
+ // Core commands
7449
+ new SlashCommandBuilder().setName("scan").setDescription("Analyze a crypto project").addStringOption(
7450
+ (opt) => opt.setName("project").setDescription("Project name or contract address").setRequired(true)
7451
+ ).addStringOption(
7452
+ (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
7453
+ ).toJSON(),
7454
+ new SlashCommandBuilder().setName("trends").setDescription("Trending tokens + market data").toJSON(),
7455
+ new SlashCommandBuilder().setName("track").setDescription("Analyze a wallet").addStringOption(
7456
+ (opt) => opt.setName("wallet").setDescription("Wallet address").setRequired(true)
7457
+ ).addStringOption(
7458
+ (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
7459
+ ).toJSON(),
7460
+ new SlashCommandBuilder().setName("ico").setDescription("Upcoming ICOs & fundraising rounds").toJSON(),
7461
+ new SlashCommandBuilder().setName("audit").setDescription("Audit a smart contract").addStringOption(
7462
+ (opt) => opt.setName("contract").setDescription("Contract address").setRequired(true)
7463
+ ).addStringOption(
7464
+ (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
7465
+ ).toJSON(),
7466
+ new SlashCommandBuilder().setName("help").setDescription("Show all Vizzor commands").toJSON(),
7467
+ // Quick commands
7468
+ new SlashCommandBuilder().setName("price").setDescription("Quick price check").addStringOption(
7469
+ (opt) => opt.setName("symbol").setDescription("Token symbol (e.g. BTC, ETH)").setRequired(true)
7470
+ ).toJSON(),
7471
+ new SlashCommandBuilder().setName("predict").setDescription("AI prediction with multi-signal analysis").addStringOption(
7472
+ (opt) => opt.setName("symbol").setDescription("Token symbol (e.g. BTC, ETH)").setRequired(true)
7473
+ ).toJSON(),
7474
+ new SlashCommandBuilder().setName("wallet").setDescription("Check wallet balance").addStringOption(
7475
+ (opt) => opt.setName("address").setDescription("Ethereum address").setRequired(true)
7476
+ ).toJSON(),
7477
+ // Agent commands
7478
+ new SlashCommandBuilder().setName("agent_create").setDescription("Create a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).addStringOption(
7479
+ (opt) => opt.setName("strategy").setDescription("Strategy (momentum, trend-following)").setRequired(false)
7480
+ ).addStringOption(
7481
+ (opt) => opt.setName("pairs").setDescription("Comma-separated pairs (e.g. BTC,ETH,SOL)").setRequired(false)
7482
+ ).toJSON(),
7483
+ new SlashCommandBuilder().setName("agent_list").setDescription("List all trading agents").toJSON(),
7484
+ new SlashCommandBuilder().setName("agent_start").setDescription("Start a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON(),
7485
+ new SlashCommandBuilder().setName("agent_stop").setDescription("Stop a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON(),
7486
+ new SlashCommandBuilder().setName("agent_status").setDescription("View agent status & recent decisions").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON(),
7487
+ new SlashCommandBuilder().setName("agent_delete").setDescription("Delete a trading agent").addStringOption((opt) => opt.setName("name").setDescription("Agent name").setRequired(true)).toJSON()
6842
7488
  ];
6843
7489
  }
6844
7490
  async function handleSlashCommand(interaction) {
@@ -7345,7 +7991,7 @@ var init_bot = __esm({
7345
7991
  "src/discord/bot.ts"() {
7346
7992
  "use strict";
7347
7993
  init_loader();
7348
- init_client();
7994
+ init_client2();
7349
7995
  init_tool_handler();
7350
7996
  init_tools();
7351
7997
  init_chat();
@@ -7688,879 +8334,452 @@ async function handlePredict(ctx) {
7688
8334
  msg += `\u{1F4C8} Composite: ${prediction.composite.toFixed(2)}
7689
8335
  `;
7690
8336
  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";
7708
- }
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}`);
7714
- }
7715
- }
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}`);
7740
- }
7741
- }
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(", ")}
7756
-
7757
- Example:
7758
- /agent_create mybot momentum BTC,ETH`
7759
- );
7760
- }
7761
- async function handleAgentCreate(ctx) {
7762
- 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
- );
7770
- return;
7771
- }
7772
- 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
7782
-
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
- `;
7802
- msg += ` Strategy: ${agent.strategy} | Pairs: ${agent.pairs.join(", ")}
7803
- `;
7804
- msg += ` Cycles: ${state?.cycleCount ?? 0} | Interval: ${agent.interval}s
7805
-
7806
- `;
7807
- }
7808
- await ctx.reply(msg);
7809
- }
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
- }
7817
- try {
7818
- const agent = getAgentByName(name);
7819
- if (!agent) {
7820
- await ctx.reply(`Agent "${name}" not found. Use /agent_list to see your agents.`);
7821
- return;
7822
- }
7823
- const state = startAgent(agent.id);
7824
- await ctx.reply(
7825
- `\u{1F7E2} Agent "${state.config.name}" started. Monitoring ${state.config.pairs.join(", ")}.`
7826
- );
7827
- } catch (error) {
7828
- const msg = error instanceof Error ? error.message : String(error);
7829
- await ctx.reply(`Failed to start agent: ${msg}`);
7830
- }
7831
- }
7832
- async function handleAgentStop(ctx) {
7833
- const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7834
- const name = args2[0];
7835
- if (!name) {
7836
- await ctx.reply("Usage: /agent_stop <name>");
7837
- return;
7838
- }
7839
- 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.`);
7847
- } catch (error) {
7848
- const msg = error instanceof Error ? error.message : String(error);
7849
- await ctx.reply(`Failed to stop agent: ${msg}`);
7850
- }
7851
- }
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}
8337
+
7876
8338
  `;
7877
- msg += `Pairs: ${state.config.pairs.join(", ")}
8339
+ msg += `Signals:
7878
8340
  `;
7879
- msg += `Interval: ${state.config.interval}s
8341
+ msg += `\u2022 Technical: ${prediction.signals.technical}
7880
8342
  `;
7881
- msg += `Cycles: ${state.cycleCount}
8343
+ msg += `\u2022 Sentiment: ${prediction.signals.sentiment}
7882
8344
  `;
7883
- if (state.error) msg += `Error: ${state.error}
8345
+ msg += `\u2022 Derivatives: ${prediction.signals.derivatives}
7884
8346
  `;
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}
8347
+ msg += `\u2022 Trend: ${prediction.signals.trend}
7892
8348
  `;
7893
- if (d.decision.reasoning.length > 0) {
7894
- msg += ` \u2192 ${d.decision.reasoning[0]}
8349
+ msg += `\u2022 Macro: ${prediction.signals.macro}
8350
+
7895
8351
  `;
7896
- }
8352
+ if (prediction.reasoning.length > 0) {
8353
+ msg += prediction.reasoning.join("\n") + "\n\n";
7897
8354
  }
8355
+ msg += prediction.disclaimer;
8356
+ await ctx.reply(msg);
8357
+ } catch (error) {
8358
+ const msg = error instanceof Error ? error.message : String(error);
8359
+ await ctx.reply(`Prediction failed: ${msg}`);
7898
8360
  }
7899
- await ctx.reply(msg);
7900
8361
  }
7901
- async function handleAgentDelete(ctx) {
8362
+ async function handleWallet(ctx) {
7902
8363
  const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
7903
- const name = args2[0];
7904
- if (!name) {
7905
- await ctx.reply("Usage: /agent_delete <name>");
8364
+ const address = args2[0];
8365
+ if (!address) {
8366
+ await ctx.reply("Usage: /wallet <ethereum_address>\nExample: /wallet 0x...");
8367
+ return;
8368
+ }
8369
+ if (!isValidAddress(address)) {
8370
+ await ctx.reply("Invalid Ethereum address. Must start with 0x followed by 40 hex characters.");
7906
8371
  return;
7907
8372
  }
7908
8373
  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.`);
8374
+ const balance = await getWalletBalance(address);
8375
+ await ctx.reply(
8376
+ `\u{1F45B} Wallet Balance
8377
+
8378
+ Address: ${address}
8379
+ Balance: ${balance} ETH
8380
+
8381
+ Use /track <address> for full forensic analysis`
8382
+ );
7916
8383
  } catch (error) {
7917
8384
  const msg = error instanceof Error ? error.message : String(error);
7918
- await ctx.reply(`Failed to delete agent: ${msg}`);
8385
+ await ctx.reply(`Wallet query failed: ${msg}`);
7919
8386
  }
7920
8387
  }
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
- });
8388
+ async function handleAgentHelp(ctx) {
8389
+ const strategies = listStrategies();
8390
+ await ctx.reply(
8391
+ `\u{1F916} Agent Management
7939
8392
 
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.");
7956
- return;
7957
- }
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);
8393
+ Commands:
8394
+ /agent_create <name> <strategy> <pairs> - Create agent
8395
+ /agent_list - List all agents
8396
+ /agent_start <name> - Start an agent
8397
+ /agent_stop <name> - Stop an agent
8398
+ /agent_status <name> - View status & decisions
8399
+ /agent_delete <name> - Delete an agent
8400
+
8401
+ Available strategies: ${strategies.join(", ")}
8402
+
8403
+ Example:
8404
+ /agent_create mybot momentum BTC,ETH`
8405
+ );
7970
8406
  }
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;
8407
+ async function handleAgentCreate(ctx) {
8408
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8409
+ const name = args2[0];
8410
+ const strategy = args2[1] ?? "momentum";
8411
+ const pairsStr = args2[2] ?? "BTC,ETH";
8412
+ if (!name) {
8413
+ await ctx.reply(
8414
+ "Usage: /agent_create <name> <strategy> <pairs>\nExample: /agent_create mybot momentum BTC,ETH,SOL"
8415
+ );
8416
+ return;
7978
8417
  }
7979
- });
8418
+ try {
8419
+ const pairs = pairsStr.split(",").map((p) => p.trim().toUpperCase());
8420
+ const agent = createAgent(name, strategy, pairs);
8421
+ await ctx.reply(
8422
+ `\u2705 Agent Created
7980
8423
 
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>");
8424
+ Name: ${agent.name}
8425
+ Strategy: ${agent.strategy}
8426
+ Pairs: ${agent.pairs.join(", ")}
8427
+ Interval: ${agent.interval}s
8428
+
8429
+ Use /agent_start ${agent.name} to activate`
8430
+ );
8431
+ } catch (error) {
8432
+ const msg = error instanceof Error ? error.message : String(error);
8433
+ await ctx.reply(`Failed to create agent: ${msg}`);
7992
8434
  }
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
8435
  }
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
- });
8041
-
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");
8436
+ async function handleAgentList(ctx) {
8437
+ const agents = listAgents();
8438
+ if (agents.length === 0) {
8439
+ await ctx.reply("No agents created yet. Use /agent_create to create one.");
8058
8440
  return;
8059
8441
  }
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());
8442
+ let msg = "\u{1F916} Your Agents\n\n";
8443
+ for (const agent of agents) {
8444
+ const state = getAgentStatus(agent.id);
8445
+ const statusEmoji = state?.status === "running" ? "\u{1F7E2}" : state?.status === "stopped" ? "\u{1F534}" : "\u26AA";
8446
+ msg += `${statusEmoji} ${agent.name} [${state?.status ?? "idle"}]
8447
+ `;
8448
+ msg += ` Strategy: ${agent.strategy} | Pairs: ${agent.pairs.join(", ")}
8449
+ `;
8450
+ msg += ` Cycles: ${state?.cycleCount ?? 0} | Interval: ${agent.interval}s
8451
+
8452
+ `;
8072
8453
  }
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);
8454
+ await ctx.reply(msg);
8080
8455
  }
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;
8456
+ async function handleAgentStart(ctx) {
8457
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8458
+ const name = args2[0];
8459
+ if (!name) {
8460
+ await ctx.reply("Usage: /agent_start <name>");
8461
+ return;
8097
8462
  }
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>"));
8463
+ try {
8464
+ const agent = getAgentByName(name);
8465
+ if (!agent) {
8466
+ await ctx.reply(`Agent "${name}" not found. Use /agent_list to see your agents.`);
8467
+ return;
8468
+ }
8469
+ const state = startAgent(agent.id);
8470
+ await ctx.reply(
8471
+ `\u{1F7E2} Agent "${state.config.name}" started. Monitoring ${state.config.pairs.join(", ")}.`
8472
+ );
8473
+ } catch (error) {
8474
+ const msg = error instanceof Error ? error.message : String(error);
8475
+ await ctx.reply(`Failed to start agent: ${msg}`);
8104
8476
  }
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>"));
8477
+ }
8478
+ async function handleAgentStop(ctx) {
8479
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8480
+ const name = args2[0];
8481
+ if (!name) {
8482
+ await ctx.reply("Usage: /agent_stop <name>");
8483
+ return;
8110
8484
  }
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}`));
8485
+ try {
8486
+ const agent = getAgentByName(name);
8487
+ if (!agent) {
8488
+ await ctx.reply(`Agent "${name}" not found.`);
8489
+ return;
8490
+ }
8491
+ const state = stopAgent(agent.id);
8492
+ await ctx.reply(`\u{1F534} Agent "${state.config.name}" stopped after ${state.cycleCount} cycles.`);
8493
+ } catch (error) {
8494
+ const msg = error instanceof Error ? error.message : String(error);
8495
+ await ctx.reply(`Failed to stop agent: ${msg}`);
8119
8496
  }
8120
8497
  }
8121
- var init_bot3 = __esm({
8122
- "src/cli/commands/bot.ts"() {
8123
- "use strict";
8124
- init_loader();
8125
- init_keys();
8498
+ async function handleAgentStatus(ctx) {
8499
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8500
+ const name = args2[0];
8501
+ if (!name) {
8502
+ await ctx.reply("Usage: /agent_status <name>");
8503
+ return;
8126
8504
  }
8127
- });
8128
-
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
- `);
8156
- }
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
- };
8167
- }
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
- };
8505
+ const agent = getAgentByName(name);
8506
+ if (!agent) {
8507
+ await ctx.reply(`Agent "${name}" not found.`);
8508
+ return;
8276
8509
  }
8277
- });
8510
+ const state = getAgentStatus(agent.id);
8511
+ if (!state) {
8512
+ await ctx.reply(`Agent "${name}" not found.`);
8513
+ return;
8514
+ }
8515
+ const statusEmoji = state.status === "running" ? "\u{1F7E2}" : state.status === "stopped" ? "\u{1F534}" : "\u26AA";
8516
+ let msg = `\u{1F916} Agent: ${state.config.name}
8278
8517
 
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
- );
8490
- }
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
- };
8518
+ `;
8519
+ msg += `${statusEmoji} Status: ${state.status}
8520
+ `;
8521
+ msg += `Strategy: ${state.config.strategy}
8522
+ `;
8523
+ msg += `Pairs: ${state.config.pairs.join(", ")}
8524
+ `;
8525
+ msg += `Interval: ${state.config.interval}s
8526
+ `;
8527
+ msg += `Cycles: ${state.cycleCount}
8528
+ `;
8529
+ if (state.error) msg += `Error: ${state.error}
8530
+ `;
8531
+ const decisions = getRecentDecisions(agent.id, 5);
8532
+ if (decisions.length > 0) {
8533
+ msg += "\nRecent Decisions:\n";
8534
+ for (const d of decisions) {
8535
+ const actionEmoji = d.decision.action === "buy" ? "\u{1F7E2}" : d.decision.action === "sell" ? "\u{1F534}" : "\u26AA";
8536
+ const time = new Date(d.timestamp).toLocaleString();
8537
+ msg += `${actionEmoji} ${d.symbol} ${d.decision.action.toUpperCase()} (${d.decision.confidence}%) \u2014 ${time}
8538
+ `;
8539
+ if (d.decision.reasoning.length > 0) {
8540
+ msg += ` \u2192 ${d.decision.reasoning[0]}
8541
+ `;
8527
8542
  }
8528
- // ---- Lifecycle -----------------------------------------------------------
8529
- async close() {
8530
- await this.pool.end();
8543
+ }
8544
+ }
8545
+ await ctx.reply(msg);
8546
+ }
8547
+ async function handleAgentDelete(ctx) {
8548
+ const args2 = ctx.message?.text?.split(" ").slice(1) ?? [];
8549
+ const name = args2[0];
8550
+ if (!name) {
8551
+ await ctx.reply("Usage: /agent_delete <name>");
8552
+ return;
8553
+ }
8554
+ try {
8555
+ const agent = getAgentByName(name);
8556
+ if (!agent) {
8557
+ await ctx.reply(`Agent "${name}" not found.`);
8558
+ return;
8559
+ }
8560
+ deleteAgent(agent.id);
8561
+ await ctx.reply(`\u{1F5D1} Agent "${name}" deleted.`);
8562
+ } catch (error) {
8563
+ const msg = error instanceof Error ? error.message : String(error);
8564
+ await ctx.reply(`Failed to delete agent: ${msg}`);
8565
+ }
8566
+ }
8567
+ var init_commands2 = __esm({
8568
+ "src/telegram/commands/index.ts"() {
8569
+ "use strict";
8570
+ init_registry();
8571
+ init_loader();
8572
+ init_project_analyzer();
8573
+ init_risk_scorer();
8574
+ init_wallet_analyzer();
8575
+ init_contract_auditor();
8576
+ init_market();
8577
+ init_ico_tracker();
8578
+ init_defillama();
8579
+ init_binance();
8580
+ init_predictor();
8581
+ init_agent();
8582
+ init_market2();
8583
+ }
8584
+ });
8585
+
8586
+ // src/telegram/middleware/rate-limit.ts
8587
+ async function rateLimitMiddleware(ctx, next) {
8588
+ const userId = ctx.from?.id;
8589
+ if (!userId) {
8590
+ await next();
8591
+ return;
8592
+ }
8593
+ const now = Date.now();
8594
+ const entry = userLimits2.get(userId);
8595
+ if (!entry || now >= entry.resetAt) {
8596
+ userLimits2.set(userId, { count: 1, resetAt: now + WINDOW_MS2 });
8597
+ await next();
8598
+ return;
8599
+ }
8600
+ if (entry.count >= MAX_REQUESTS2) {
8601
+ await ctx.reply("Rate limited. Please wait a moment before sending more commands.");
8602
+ return;
8603
+ }
8604
+ entry.count++;
8605
+ await next();
8606
+ }
8607
+ function startRateLimitCleanup2(intervalMs = 3e5) {
8608
+ return setInterval(() => {
8609
+ const now = Date.now();
8610
+ for (const [userId, entry] of userLimits2) {
8611
+ if (now >= entry.resetAt) {
8612
+ userLimits2.delete(userId);
8531
8613
  }
8532
- };
8614
+ }
8615
+ }, intervalMs);
8616
+ }
8617
+ var userLimits2, MAX_REQUESTS2, WINDOW_MS2;
8618
+ var init_rate_limit2 = __esm({
8619
+ "src/telegram/middleware/rate-limit.ts"() {
8620
+ "use strict";
8621
+ userLimits2 = /* @__PURE__ */ new Map();
8622
+ MAX_REQUESTS2 = 10;
8623
+ WINDOW_MS2 = 6e4;
8533
8624
  }
8534
8625
  });
8535
8626
 
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);
8627
+ // src/telegram/bot.ts
8628
+ var bot_exports2 = {};
8629
+ __export(bot_exports2, {
8630
+ startTelegramBot: () => startTelegramBot
8631
+ });
8632
+ import { Bot } from "grammy";
8633
+ async function startTelegramBot() {
8634
+ const config2 = loadConfig();
8635
+ const token = config2.telegramToken;
8636
+ if (!token) {
8637
+ throw new Error("Telegram token not configured. Run: vizzor config set telegramToken <token>");
8638
+ }
8639
+ setConfig(config2);
8640
+ setToolHandler(handleTool);
8641
+ const bot = new Bot(token);
8642
+ bot.use(rateLimitMiddleware);
8643
+ registerCommands(bot);
8644
+ bot.on("message:text", async (ctx) => {
8645
+ const text = ctx.message.text;
8646
+ if (text.startsWith("/")) return;
8647
+ await ctx.reply("\u{1F52E} Analyzing...");
8648
+ try {
8649
+ const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
8650
+ const chunks = splitMessage(response, 4e3);
8651
+ for (const chunk of chunks) {
8652
+ await ctx.reply(chunk);
8653
+ }
8654
+ } catch (error) {
8655
+ const msg = error instanceof Error ? error.message : String(error);
8656
+ await ctx.reply(`Analysis failed: ${msg}`);
8657
+ }
8658
+ });
8659
+ bot.catch((err) => {
8660
+ logger2.error({ err: err.message }, "Telegram bot error");
8661
+ });
8662
+ const cleanupInterval = startRateLimitCleanup2();
8663
+ logger2.info("Telegram bot starting...");
8664
+ await bot.start({
8665
+ onStart: (botInfo) => {
8666
+ logger2.info(`Telegram bot started as @${botInfo.username}`);
8667
+ }
8668
+ });
8669
+ clearInterval(cleanupInterval);
8670
+ }
8671
+ var logger2;
8672
+ var init_bot2 = __esm({
8673
+ "src/telegram/bot.ts"() {
8674
+ "use strict";
8675
+ init_loader();
8676
+ init_client2();
8677
+ init_tool_handler();
8678
+ init_tools();
8679
+ init_chat();
8680
+ init_message_split();
8681
+ init_commands2();
8682
+ init_rate_limit2();
8683
+ init_logger();
8684
+ logger2 = createLogger("telegram-bot");
8685
+ }
8686
+ });
8687
+
8688
+ // src/cli/commands/bot.ts
8689
+ var bot_exports3 = {};
8690
+ __export(bot_exports3, {
8691
+ handleBotStart: () => handleBotStart,
8692
+ handleBotValidate: () => handleBotValidate
8693
+ });
8694
+ import chalk7 from "chalk";
8695
+ async function handleBotStart(options) {
8696
+ const config2 = getConfig();
8697
+ const startDiscord = options.discord || options.all;
8698
+ const startTelegram = options.telegram || options.all;
8699
+ if (!startDiscord && !startTelegram) {
8700
+ console.log(chalk7.yellow("Specify which bot to start:"));
8701
+ console.log(" --discord Start Discord bot");
8702
+ console.log(" --telegram Start Telegram bot");
8703
+ console.log(" --all Start all bots");
8704
+ return;
8705
+ }
8706
+ const promises = [];
8707
+ if (startDiscord) {
8708
+ requireKey("DISCORD_TOKEN", config2.discordToken);
8709
+ console.log(chalk7.blue("Starting Discord bot..."));
8710
+ const { startDiscordBot: startDiscordBot2 } = await Promise.resolve().then(() => (init_bot(), bot_exports));
8711
+ promises.push(startDiscordBot2());
8712
+ }
8713
+ if (startTelegram) {
8714
+ requireKey("TELEGRAM_BOT_TOKEN", config2.telegramToken);
8715
+ console.log(chalk7.blue("Starting Telegram bot..."));
8716
+ const { startTelegramBot: startTelegramBot2 } = await Promise.resolve().then(() => (init_bot2(), bot_exports2));
8717
+ promises.push(startTelegramBot2());
8718
+ }
8719
+ const shutdown = () => {
8720
+ console.log(chalk7.dim("\nShutting down bots..."));
8721
+ process.exit(0);
8722
+ };
8723
+ process.on("SIGINT", shutdown);
8724
+ process.on("SIGTERM", shutdown);
8725
+ await Promise.all(promises);
8726
+ }
8727
+ function handleBotValidate() {
8728
+ const config2 = getConfig();
8729
+ console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
8730
+ const checks = [
8731
+ { label: "Anthropic API Key", isSet: hasKey(config2.anthropicApiKey), required: true },
8732
+ { label: "Etherscan API Key", isSet: hasKey(config2.etherscanApiKey), required: true },
8733
+ { label: "Discord Token", isSet: hasKey(config2.discordToken), required: false },
8734
+ { label: "Discord Guild ID", isSet: hasKey(config2.discordGuildId), required: false },
8735
+ { label: "Telegram Token", isSet: hasKey(config2.telegramToken), required: false },
8736
+ { label: "CryptoPanic Key", isSet: hasKey(config2.cryptopanicApiKey), required: false }
8737
+ ];
8738
+ let allRequired = true;
8739
+ for (const check of checks) {
8740
+ const status = check.isSet ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
8741
+ console.log(` ${status.padEnd(18)} ${check.label}`);
8742
+ if (check.required && !check.isSet) allRequired = false;
8743
+ }
8744
+ console.log();
8745
+ if (hasKey(config2.discordToken)) {
8746
+ console.log(chalk7.green(" Discord bot: Ready to start"));
8542
8747
  } else {
8543
- instance = new SqliteStore();
8748
+ console.log(chalk7.yellow(" Discord bot: Set discordToken to enable"));
8749
+ console.log(chalk7.dim(" vizzor config set discordToken <token>"));
8750
+ }
8751
+ if (hasKey(config2.telegramToken)) {
8752
+ console.log(chalk7.green(" Telegram bot: Ready to start"));
8753
+ } else {
8754
+ console.log(chalk7.yellow(" Telegram bot: Set telegramToken to enable"));
8755
+ console.log(chalk7.dim(" vizzor config set telegramToken <token>"));
8756
+ }
8757
+ console.log();
8758
+ if (!allRequired) {
8759
+ console.log(chalk7.red("Required keys are missing. Run: vizzor config set <key> <value>"));
8760
+ } else if (hasKey(config2.discordToken) && hasKey(config2.telegramToken)) {
8761
+ console.log(chalk7.green("All bots ready. Run: vizzor bot start --all"));
8762
+ } else if (hasKey(config2.discordToken) || hasKey(config2.telegramToken)) {
8763
+ const which = hasKey(config2.discordToken) ? "--discord" : "--telegram";
8764
+ console.log(chalk7.green(`Bot ready. Run: vizzor bot start ${which}`));
8544
8765
  }
8545
- return instance;
8546
8766
  }
8547
- var instance;
8548
- var init_store_factory = __esm({
8549
- "src/data/store-factory.ts"() {
8767
+ var init_bot3 = __esm({
8768
+ "src/cli/commands/bot.ts"() {
8550
8769
  "use strict";
8551
- init_sqlite_store();
8552
- instance = null;
8770
+ init_loader();
8771
+ init_keys();
8553
8772
  }
8554
8773
  });
8555
8774
 
8556
8775
  // src/data/collector.ts
8557
- var log, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
8776
+ var log2, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
8558
8777
  var init_collector = __esm({
8559
8778
  "src/data/collector.ts"() {
8560
8779
  "use strict";
8561
8780
  init_binance();
8562
8781
  init_logger();
8563
- log = createLogger("collector");
8782
+ log2 = createLogger("collector");
8564
8783
  MAJOR_SYMBOLS = [
8565
8784
  "BTC",
8566
8785
  "ETH",
@@ -8611,10 +8830,10 @@ var init_collector = __esm({
8611
8830
  }
8612
8831
  start() {
8613
8832
  if (this.status.running) {
8614
- log.warn("Collector already running");
8833
+ log2.warn("Collector already running");
8615
8834
  return;
8616
8835
  }
8617
- log.info(
8836
+ log2.info(
8618
8837
  `Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
8619
8838
  );
8620
8839
  this.status.running = true;
@@ -8627,10 +8846,10 @@ var init_collector = __esm({
8627
8846
  this.timer = null;
8628
8847
  }
8629
8848
  this.status.running = false;
8630
- log.info("Data collector stopped");
8849
+ log2.info("Data collector stopped");
8631
8850
  }
8632
8851
  async collectAll() {
8633
- log.info("Collection cycle starting");
8852
+ log2.info("Collection cycle starting");
8634
8853
  const start = Date.now();
8635
8854
  for (const timeframe of TIMEFRAMES) {
8636
8855
  for (const symbol of this.symbols) {
@@ -8638,7 +8857,7 @@ var init_collector = __esm({
8638
8857
  await this.collectSymbol(symbol, timeframe);
8639
8858
  } catch (err) {
8640
8859
  this.status.errors++;
8641
- log.error(
8860
+ log2.error(
8642
8861
  `Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
8643
8862
  );
8644
8863
  }
@@ -8646,7 +8865,7 @@ var init_collector = __esm({
8646
8865
  }
8647
8866
  this.status.lastRun = Date.now();
8648
8867
  const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
8649
- log.info(
8868
+ log2.info(
8650
8869
  `Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
8651
8870
  );
8652
8871
  }
@@ -9473,7 +9692,7 @@ function useAIStream() {
9473
9692
  var init_use_ai_stream = __esm({
9474
9693
  "src/tui/hooks/use-ai-stream.ts"() {
9475
9694
  "use strict";
9476
- init_client();
9695
+ init_client2();
9477
9696
  init_chat();
9478
9697
  init_tools();
9479
9698
  init_context_injector();
@@ -10025,7 +10244,7 @@ var init_commands3 = __esm({
10025
10244
  init_constants();
10026
10245
  init_binance();
10027
10246
  init_agent();
10028
- init_client();
10247
+ init_client2();
10029
10248
  init_registry2();
10030
10249
  init_types();
10031
10250
  }
@@ -10467,7 +10686,7 @@ var init_app = __esm({
10467
10686
  init_commands3();
10468
10687
  init_loader();
10469
10688
  init_constants();
10470
- init_client();
10689
+ init_client2();
10471
10690
  init_tool_handler();
10472
10691
  init_tools();
10473
10692
  init_binance();