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