@vizzor/cli 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -46,6 +46,19 @@ var init_schema = __esm({
46
46
  walletData: z.number().default(600),
47
47
  contractCode: z.number().default(86400)
48
48
  }).default(() => ({ tokenInfo: 3600, marketData: 300, walletData: 600, contractCode: 86400 })),
49
+ database: z.object({
50
+ type: z.enum(["sqlite", "postgres"]).default("sqlite"),
51
+ url: z.string().optional()
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
+ })),
49
62
  discordToken: z.string().optional(),
50
63
  discordGuildId: z.string().optional(),
51
64
  telegramToken: z.string().optional()
@@ -643,12 +656,12 @@ var init_adapter = __esm({
643
656
  toBlock: options?.toBlock,
644
657
  args: options?.args
645
658
  });
646
- return logs.map((log) => ({
647
- eventName: log.eventName ?? eventName,
648
- blockNumber: log.blockNumber ?? 0n,
649
- transactionHash: log.transactionHash ?? "0x",
650
- args: log.args ?? {},
651
- logIndex: log.logIndex ?? 0
659
+ return logs.map((log3) => ({
660
+ eventName: log3.eventName ?? eventName,
661
+ blockNumber: log3.blockNumber ?? 0n,
662
+ transactionHash: log3.transactionHash ?? "0x",
663
+ args: log3.args ?? {},
664
+ logIndex: log3.logIndex ?? 0
652
665
  }));
653
666
  }
654
667
  // ── Tokens ──────────────────────────────────────────────────────────────
@@ -1710,6 +1723,111 @@ var init_fear_greed = __esm({
1710
1723
  }
1711
1724
  });
1712
1725
 
1726
+ // src/utils/logger.ts
1727
+ import pino from "pino";
1728
+ function createLogger(name) {
1729
+ const isDev = process.env["NODE_ENV"] !== "production";
1730
+ if (isDev) {
1731
+ return pino({
1732
+ name,
1733
+ level,
1734
+ transport: {
1735
+ target: "pino-pretty",
1736
+ options: {
1737
+ colorize: true
1738
+ }
1739
+ }
1740
+ });
1741
+ }
1742
+ return pino({ name, level });
1743
+ }
1744
+ var level;
1745
+ var init_logger = __esm({
1746
+ "src/utils/logger.ts"() {
1747
+ "use strict";
1748
+ level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
1749
+ }
1750
+ });
1751
+
1752
+ // src/ml/client.ts
1753
+ function getMLClient() {
1754
+ return mlClient;
1755
+ }
1756
+ var log, mlClient;
1757
+ var init_client = __esm({
1758
+ "src/ml/client.ts"() {
1759
+ "use strict";
1760
+ init_logger();
1761
+ log = createLogger("ml-client");
1762
+ mlClient = null;
1763
+ }
1764
+ });
1765
+
1766
+ // src/ml/feature-engineer.ts
1767
+ async function buildFeatureVector(symbol) {
1768
+ const [ta, fundingResult, tickerResult, fgResult, klines] = await Promise.allSettled([
1769
+ analyzeTechnicals(symbol, "4h"),
1770
+ fetchFundingRate(symbol),
1771
+ fetchTickerPrice(symbol),
1772
+ fetchFearGreedIndex(1),
1773
+ fetchKlines(symbol, "4h", 100)
1774
+ ]);
1775
+ const indicators = ta.status === "fulfilled" ? ta.value.indicators : null;
1776
+ const funding = fundingResult.status === "fulfilled" ? fundingResult.value : null;
1777
+ const ticker = tickerResult.status === "fulfilled" ? tickerResult.value : null;
1778
+ const fg = fgResult.status === "fulfilled" ? fgResult.value : null;
1779
+ const candles = klines.status === "fulfilled" ? klines.value : [];
1780
+ let rsiSlope = 0;
1781
+ if (candles.length >= 17) {
1782
+ const closes = candles.map((k) => k.close);
1783
+ const recentRsi = calculateRSI(closes, 14);
1784
+ const olderCloses = closes.slice(0, -3);
1785
+ const olderRsi = calculateRSI(olderCloses, 14);
1786
+ if (recentRsi !== null && olderRsi !== null) {
1787
+ rsiSlope = recentRsi - olderRsi;
1788
+ }
1789
+ }
1790
+ let volumeRatio = 1;
1791
+ if (candles.length >= 21) {
1792
+ const currentVolume = candles[candles.length - 1].volume;
1793
+ const avgVolume = candles.slice(-21, -1).reduce((sum, k) => sum + k.volume, 0) / 20;
1794
+ volumeRatio = avgVolume > 0 ? currentVolume / avgVolume : 1;
1795
+ }
1796
+ const price = ticker?.price ?? candles[candles.length - 1]?.close ?? 0;
1797
+ const ema12 = indicators?.ema12 ?? 0;
1798
+ const ema26 = indicators?.ema26 ?? 0;
1799
+ const emaCrossoverPct = price > 0 ? (ema12 - ema26) / price * 100 : 0;
1800
+ const atr = indicators?.atr ?? 0;
1801
+ const atrPct = price > 0 ? atr / price * 100 : 0;
1802
+ return {
1803
+ rsi: indicators?.rsi ?? 50,
1804
+ macdHistogram: indicators?.macd?.histogram ?? 0,
1805
+ bollingerPercentB: indicators?.bollingerBands?.percentB ?? 0.5,
1806
+ ema12,
1807
+ ema26,
1808
+ atr,
1809
+ obv: indicators?.obv ?? 0,
1810
+ fundingRate: funding?.fundingRate ?? 0,
1811
+ fearGreed: fg?.current.value ?? 50,
1812
+ priceChange24h: ticker?.change24h ?? 0,
1813
+ rsiSlope,
1814
+ volumeRatio,
1815
+ emaCrossoverPct,
1816
+ atrPct,
1817
+ symbol: symbol.toUpperCase(),
1818
+ timestamp: Date.now()
1819
+ };
1820
+ }
1821
+ var init_feature_engineer = __esm({
1822
+ "src/ml/feature-engineer.ts"() {
1823
+ "use strict";
1824
+ init_technical_analysis();
1825
+ init_binance();
1826
+ init_fear_greed();
1827
+ init_indicators();
1828
+ }
1829
+ });
1830
+
1713
1831
  // src/core/trends/predictor.ts
1714
1832
  async function generatePrediction(symbol) {
1715
1833
  const reasoning = [];
@@ -1830,7 +1948,7 @@ async function generatePrediction(symbol) {
1830
1948
  const negativeCount = signalValues.filter((v) => v < 0).length;
1831
1949
  const agreement = Math.max(positiveCount, negativeCount) / Math.max(1, positiveCount + negativeCount);
1832
1950
  const confidence = Math.round(Math.min(95, completeness / 5 * agreement * 100));
1833
- return {
1951
+ const rulePrediction = {
1834
1952
  symbol: symbol.toUpperCase(),
1835
1953
  direction,
1836
1954
  confidence,
@@ -1840,6 +1958,34 @@ async function generatePrediction(symbol) {
1840
1958
  composite: Math.round(composite),
1841
1959
  disclaimer: "This is not financial advice. Predictions are based on historical data and AI analysis. Always do your own research."
1842
1960
  };
1961
+ const mlClient2 = getMLClient();
1962
+ if (mlClient2) {
1963
+ try {
1964
+ const features = await buildFeatureVector(symbol);
1965
+ const mlPred = await mlClient2.predict(features);
1966
+ if (mlPred) {
1967
+ return mergePredictions(rulePrediction, mlPred);
1968
+ }
1969
+ } catch {
1970
+ }
1971
+ }
1972
+ return rulePrediction;
1973
+ }
1974
+ function mergePredictions(rule, ml) {
1975
+ const mlComposite = ml.direction === "up" ? ml.probability * 100 : ml.direction === "down" ? -(ml.probability * 100) : 0;
1976
+ const mergedComposite = Math.round(rule.composite * 0.4 + mlComposite * 0.6);
1977
+ const mergedDirection = mergedComposite > 15 ? "up" : mergedComposite < -15 ? "down" : "sideways";
1978
+ const mergedConfidence = Math.round(Math.min(95, rule.confidence * 0.4 + ml.confidence * 0.6));
1979
+ return {
1980
+ ...rule,
1981
+ direction: mergedDirection,
1982
+ confidence: mergedConfidence,
1983
+ composite: mergedComposite,
1984
+ reasoning: [
1985
+ ...rule.reasoning,
1986
+ `ML (${ml.model}): ${ml.direction} with ${(ml.probability * 100).toFixed(1)}% probability (horizon: ${ml.horizon})`
1987
+ ]
1988
+ };
1843
1989
  }
1844
1990
  var WEIGHTS2;
1845
1991
  var init_predictor = __esm({
@@ -1849,6 +1995,8 @@ var init_predictor = __esm({
1849
1995
  init_sentiment();
1850
1996
  init_fear_greed();
1851
1997
  init_binance();
1998
+ init_client();
1999
+ init_feature_engineer();
1852
2000
  WEIGHTS2 = {
1853
2001
  technical: 40,
1854
2002
  sentiment: 20,
@@ -2561,7 +2709,17 @@ async function handleConfigSet(key, value) {
2561
2709
  }
2562
2710
  await loadConfig();
2563
2711
  const config2 = getConfig();
2564
- const updatedConfig = { ...config2, [key]: value };
2712
+ const updatedConfig = JSON.parse(JSON.stringify(config2));
2713
+ if (key.includes(".")) {
2714
+ const [section, field] = key.split(".");
2715
+ if (!updatedConfig[section] || typeof updatedConfig[section] !== "object") {
2716
+ updatedConfig[section] = {};
2717
+ }
2718
+ const parsed = field === "maxTokens" ? Number(value) : value;
2719
+ updatedConfig[section][field] = parsed;
2720
+ } else {
2721
+ updatedConfig[key] = value;
2722
+ }
2565
2723
  writeFileSync2(configPath, yamlStringify(updatedConfig), "utf-8");
2566
2724
  const displayValue = isSensitive ? maskKey(value) : value;
2567
2725
  console.log(chalk6.green(`Set ${key} = ${displayValue}`));
@@ -5219,7 +5377,7 @@ async function analyze(systemPrompt, userMessage, tools) {
5219
5377
  return p.analyze(systemPrompt, userMessage, tools, toolHandler);
5220
5378
  }
5221
5379
  var provider, config, toolHandler;
5222
- var init_client = __esm({
5380
+ var init_client2 = __esm({
5223
5381
  "src/ai/client.ts"() {
5224
5382
  "use strict";
5225
5383
  init_registry2();
@@ -5514,6 +5672,26 @@ function getDb() {
5514
5672
  `);
5515
5673
  return db;
5516
5674
  }
5675
+ function getCached(key) {
5676
+ const row = getDb().prepare("SELECT value, expires_at FROM cache WHERE key = ?").get(key);
5677
+ if (!row) {
5678
+ return null;
5679
+ }
5680
+ const now = Math.floor(Date.now() / 1e3);
5681
+ if (row.expires_at <= now) {
5682
+ getDb().prepare("DELETE FROM cache WHERE key = ?").run(key);
5683
+ return null;
5684
+ }
5685
+ return JSON.parse(row.value);
5686
+ }
5687
+ function setCache(key, value, ttlSeconds) {
5688
+ const now = Math.floor(Date.now() / 1e3);
5689
+ const expiresAt = now + ttlSeconds;
5690
+ getDb().prepare(
5691
+ `INSERT OR REPLACE INTO cache (key, value, expires_at, created_at)
5692
+ VALUES (?, ?, ?, ?)`
5693
+ ).run(key, JSON.stringify(value), expiresAt, now);
5694
+ }
5517
5695
  var db;
5518
5696
  var init_cache = __esm({
5519
5697
  "src/data/cache.ts"() {
@@ -5523,32 +5701,6 @@ var init_cache = __esm({
5523
5701
  }
5524
5702
  });
5525
5703
 
5526
- // src/utils/logger.ts
5527
- import pino from "pino";
5528
- function createLogger(name) {
5529
- const isDev = process.env["NODE_ENV"] !== "production";
5530
- if (isDev) {
5531
- return pino({
5532
- name,
5533
- level,
5534
- transport: {
5535
- target: "pino-pretty",
5536
- options: {
5537
- colorize: true
5538
- }
5539
- }
5540
- });
5541
- }
5542
- return pino({ name, level });
5543
- }
5544
- var level;
5545
- var init_logger = __esm({
5546
- "src/utils/logger.ts"() {
5547
- "use strict";
5548
- level = process.env["VIZZOR_LOG_LEVEL"] ?? "info";
5549
- }
5550
- });
5551
-
5552
5704
  // src/core/agent/engine.ts
5553
5705
  var logger, AgentEngine;
5554
5706
  var init_engine = __esm({
@@ -6019,6 +6171,436 @@ var init_agent = __esm({
6019
6171
  }
6020
6172
  });
6021
6173
 
6174
+ // src/data/sqlite-store.ts
6175
+ function ensureAgentTables2() {
6176
+ const db2 = getDb();
6177
+ db2.exec(`
6178
+ CREATE TABLE IF NOT EXISTS agents (
6179
+ id TEXT PRIMARY KEY,
6180
+ name TEXT NOT NULL UNIQUE,
6181
+ strategy TEXT NOT NULL,
6182
+ pairs TEXT NOT NULL,
6183
+ interval_seconds INTEGER NOT NULL DEFAULT 60,
6184
+ created_at INTEGER NOT NULL,
6185
+ updated_at INTEGER NOT NULL
6186
+ )
6187
+ `);
6188
+ db2.exec(`
6189
+ CREATE TABLE IF NOT EXISTS agent_decisions (
6190
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6191
+ agent_id TEXT NOT NULL,
6192
+ symbol TEXT NOT NULL,
6193
+ action TEXT NOT NULL,
6194
+ confidence INTEGER NOT NULL,
6195
+ reasoning TEXT NOT NULL,
6196
+ signals TEXT NOT NULL,
6197
+ created_at INTEGER NOT NULL,
6198
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
6199
+ )
6200
+ `);
6201
+ }
6202
+ function rowToConfig(row) {
6203
+ return {
6204
+ id: row.id,
6205
+ name: row.name,
6206
+ strategy: row.strategy,
6207
+ pairs: JSON.parse(row.pairs),
6208
+ interval: row.interval_seconds,
6209
+ createdAt: row.created_at,
6210
+ updatedAt: row.updated_at
6211
+ };
6212
+ }
6213
+ var SqliteStore;
6214
+ var init_sqlite_store = __esm({
6215
+ "src/data/sqlite-store.ts"() {
6216
+ "use strict";
6217
+ init_cache();
6218
+ SqliteStore = class {
6219
+ // ---- Cache ---------------------------------------------------------------
6220
+ async getCached(key) {
6221
+ return getCached(key);
6222
+ }
6223
+ async setCache(key, value, ttlSeconds) {
6224
+ setCache(key, value, ttlSeconds);
6225
+ }
6226
+ // ---- Agents --------------------------------------------------------------
6227
+ async createAgent(config2) {
6228
+ ensureAgentTables2();
6229
+ getDb().prepare(
6230
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
6231
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
6232
+ ).run(
6233
+ config2.id,
6234
+ config2.name,
6235
+ config2.strategy,
6236
+ JSON.stringify(config2.pairs),
6237
+ config2.interval,
6238
+ config2.createdAt,
6239
+ config2.updatedAt
6240
+ );
6241
+ return config2;
6242
+ }
6243
+ async listAgents() {
6244
+ ensureAgentTables2();
6245
+ const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
6246
+ return rows.map(rowToConfig);
6247
+ }
6248
+ async getAgentById(id) {
6249
+ ensureAgentTables2();
6250
+ const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
6251
+ return row ? rowToConfig(row) : null;
6252
+ }
6253
+ async getAgentByName(name) {
6254
+ ensureAgentTables2();
6255
+ const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
6256
+ return row ? rowToConfig(row) : null;
6257
+ }
6258
+ async deleteAgent(id) {
6259
+ ensureAgentTables2();
6260
+ const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
6261
+ getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
6262
+ return result.changes > 0;
6263
+ }
6264
+ async logDecision(result) {
6265
+ ensureAgentTables2();
6266
+ getDb().prepare(
6267
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
6268
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
6269
+ ).run(
6270
+ result.agentId,
6271
+ result.symbol,
6272
+ result.decision.action,
6273
+ result.decision.confidence,
6274
+ JSON.stringify(result.decision.reasoning),
6275
+ JSON.stringify(result.signals),
6276
+ result.timestamp
6277
+ );
6278
+ }
6279
+ async getDecisions(agentId, limit) {
6280
+ ensureAgentTables2();
6281
+ const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
6282
+ return rows.map((r) => ({
6283
+ agentId: r.agent_id,
6284
+ symbol: r.symbol,
6285
+ timestamp: r.created_at,
6286
+ signals: JSON.parse(r.signals),
6287
+ decision: {
6288
+ action: r.action,
6289
+ confidence: r.confidence,
6290
+ reasoning: JSON.parse(r.reasoning)
6291
+ }
6292
+ }));
6293
+ }
6294
+ // ---- Time-series (no-op for SQLite) --------------------------------------
6295
+ async insertOHLCV(_records) {
6296
+ }
6297
+ async queryOHLCV(_symbol, _timeframe, _from, _to) {
6298
+ return [];
6299
+ }
6300
+ // ---- Predictions (no-op for SQLite) --------------------------------------
6301
+ async logPrediction(_prediction) {
6302
+ }
6303
+ async getPredictionAccuracy(_model, _days) {
6304
+ return {
6305
+ model: _model,
6306
+ totalPredictions: 0,
6307
+ correctPredictions: 0,
6308
+ accuracy: 0,
6309
+ byDirection: {
6310
+ up: { total: 0, correct: 0, accuracy: 0 },
6311
+ down: { total: 0, correct: 0, accuracy: 0 },
6312
+ sideways: { total: 0, correct: 0, accuracy: 0 }
6313
+ },
6314
+ period: `${_days}d`
6315
+ };
6316
+ }
6317
+ // ---- Lifecycle -----------------------------------------------------------
6318
+ async close() {
6319
+ }
6320
+ };
6321
+ }
6322
+ });
6323
+
6324
+ // src/data/postgres-store.ts
6325
+ var postgres_store_exports = {};
6326
+ __export(postgres_store_exports, {
6327
+ PostgresStore: () => PostgresStore
6328
+ });
6329
+ import pg from "pg";
6330
+ import { readFileSync as readFileSync2 } from "fs";
6331
+ import { resolve as resolve2, dirname } from "path";
6332
+ import { fileURLToPath } from "url";
6333
+ var __filename, __dirname, PostgresStore;
6334
+ var init_postgres_store = __esm({
6335
+ "src/data/postgres-store.ts"() {
6336
+ "use strict";
6337
+ __filename = fileURLToPath(import.meta.url);
6338
+ __dirname = dirname(__filename);
6339
+ PostgresStore = class {
6340
+ pool;
6341
+ initialized = false;
6342
+ constructor(connectionUrl) {
6343
+ this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
6344
+ }
6345
+ async init() {
6346
+ if (this.initialized) return;
6347
+ const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
6348
+ const sql = readFileSync2(migrationPath, "utf-8");
6349
+ await this.pool.query(sql);
6350
+ this.initialized = true;
6351
+ }
6352
+ async query(text, params) {
6353
+ await this.init();
6354
+ return this.pool.query(text, params);
6355
+ }
6356
+ // ---- Cache ---------------------------------------------------------------
6357
+ async getCached(key) {
6358
+ const now = Math.floor(Date.now() / 1e3);
6359
+ const { rows } = await this.query(
6360
+ "SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
6361
+ [key, now]
6362
+ );
6363
+ if (rows.length === 0) return null;
6364
+ return rows[0].value;
6365
+ }
6366
+ async setCache(key, value, ttlSeconds) {
6367
+ const now = Math.floor(Date.now() / 1e3);
6368
+ const expiresAt = now + ttlSeconds;
6369
+ await this.query(
6370
+ `INSERT INTO cache (key, value, expires_at, created_at)
6371
+ VALUES ($1, $2, $3, $4)
6372
+ ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
6373
+ [key, JSON.stringify(value), expiresAt, now]
6374
+ );
6375
+ }
6376
+ // ---- Agents --------------------------------------------------------------
6377
+ async createAgent(config2) {
6378
+ await this.query(
6379
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
6380
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6381
+ [
6382
+ config2.id,
6383
+ config2.name,
6384
+ config2.strategy,
6385
+ JSON.stringify(config2.pairs),
6386
+ config2.interval,
6387
+ config2.createdAt,
6388
+ config2.updatedAt
6389
+ ]
6390
+ );
6391
+ return config2;
6392
+ }
6393
+ async listAgents() {
6394
+ const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
6395
+ return rows.map((r) => ({
6396
+ id: r.id,
6397
+ name: r.name,
6398
+ strategy: r.strategy,
6399
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6400
+ interval: r.interval_seconds,
6401
+ createdAt: Number(r.created_at),
6402
+ updatedAt: Number(r.updated_at)
6403
+ }));
6404
+ }
6405
+ async getAgentById(id) {
6406
+ const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
6407
+ if (rows.length === 0) return null;
6408
+ const r = rows[0];
6409
+ return {
6410
+ id: r.id,
6411
+ name: r.name,
6412
+ strategy: r.strategy,
6413
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6414
+ interval: r.interval_seconds,
6415
+ createdAt: Number(r.created_at),
6416
+ updatedAt: Number(r.updated_at)
6417
+ };
6418
+ }
6419
+ async getAgentByName(name) {
6420
+ const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
6421
+ if (rows.length === 0) return null;
6422
+ const r = rows[0];
6423
+ return {
6424
+ id: r.id,
6425
+ name: r.name,
6426
+ strategy: r.strategy,
6427
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
6428
+ interval: r.interval_seconds,
6429
+ createdAt: Number(r.created_at),
6430
+ updatedAt: Number(r.updated_at)
6431
+ };
6432
+ }
6433
+ async deleteAgent(id) {
6434
+ const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
6435
+ return (result.rowCount ?? 0) > 0;
6436
+ }
6437
+ async logDecision(result) {
6438
+ await this.query(
6439
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
6440
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6441
+ [
6442
+ result.agentId,
6443
+ result.symbol,
6444
+ result.decision.action,
6445
+ result.decision.confidence,
6446
+ JSON.stringify(result.decision.reasoning),
6447
+ JSON.stringify(result.signals),
6448
+ result.timestamp
6449
+ ]
6450
+ );
6451
+ }
6452
+ async getDecisions(agentId, limit) {
6453
+ const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
6454
+ agentId,
6455
+ limit
6456
+ ]);
6457
+ return rows.map((r) => ({
6458
+ agentId: r.agent_id,
6459
+ symbol: r.symbol,
6460
+ timestamp: Number(r.created_at),
6461
+ signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
6462
+ decision: {
6463
+ action: r.action,
6464
+ confidence: r.confidence,
6465
+ reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
6466
+ }
6467
+ }));
6468
+ }
6469
+ // ---- Time-series ---------------------------------------------------------
6470
+ async insertOHLCV(records) {
6471
+ if (records.length === 0) return;
6472
+ const values = [];
6473
+ const placeholders = [];
6474
+ for (let i = 0; i < records.length; i++) {
6475
+ const r = records[i];
6476
+ const offset = i * 9;
6477
+ placeholders.push(
6478
+ `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
6479
+ );
6480
+ values.push(
6481
+ new Date(r.time).toISOString(),
6482
+ r.symbol,
6483
+ r.timeframe,
6484
+ r.open,
6485
+ r.high,
6486
+ r.low,
6487
+ r.close,
6488
+ r.volume,
6489
+ r.trades
6490
+ );
6491
+ }
6492
+ await this.query(
6493
+ `INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
6494
+ VALUES ${placeholders.join(", ")}
6495
+ ON CONFLICT (symbol, timeframe, time) DO UPDATE
6496
+ SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
6497
+ close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
6498
+ values
6499
+ );
6500
+ }
6501
+ async queryOHLCV(symbol, timeframe, from, to) {
6502
+ const { rows } = await this.query(
6503
+ `SELECT * FROM ohlcv
6504
+ WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
6505
+ ORDER BY time ASC`,
6506
+ [symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
6507
+ );
6508
+ return rows.map((r) => ({
6509
+ time: new Date(r.time).getTime(),
6510
+ symbol: r.symbol,
6511
+ timeframe: r.timeframe,
6512
+ open: r.open,
6513
+ high: r.high,
6514
+ low: r.low,
6515
+ close: r.close,
6516
+ volume: r.volume,
6517
+ trades: r.trades
6518
+ }));
6519
+ }
6520
+ // ---- Predictions ---------------------------------------------------------
6521
+ async logPrediction(prediction) {
6522
+ await this.query(
6523
+ `INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
6524
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
6525
+ [
6526
+ prediction.symbol,
6527
+ prediction.model,
6528
+ prediction.direction,
6529
+ prediction.probability,
6530
+ prediction.horizon,
6531
+ JSON.stringify(prediction.features),
6532
+ new Date(prediction.predictedAt).toISOString()
6533
+ ]
6534
+ );
6535
+ }
6536
+ async getPredictionAccuracy(model, days) {
6537
+ const since = new Date(Date.now() - days * 864e5).toISOString();
6538
+ const { rows: totals } = await this.query(
6539
+ `SELECT direction,
6540
+ COUNT(*)::text AS total,
6541
+ COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
6542
+ FROM predictions
6543
+ WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
6544
+ GROUP BY direction`,
6545
+ [model, since]
6546
+ );
6547
+ const byDir = {
6548
+ up: { total: 0, correct: 0, accuracy: 0 },
6549
+ down: { total: 0, correct: 0, accuracy: 0 },
6550
+ sideways: { total: 0, correct: 0, accuracy: 0 }
6551
+ };
6552
+ let totalAll = 0;
6553
+ let correctAll = 0;
6554
+ for (const row of totals) {
6555
+ const t = parseInt(row.total, 10);
6556
+ const c = parseInt(row.correct, 10);
6557
+ totalAll += t;
6558
+ correctAll += c;
6559
+ const dir = row.direction;
6560
+ if (byDir[dir]) {
6561
+ byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
6562
+ }
6563
+ }
6564
+ return {
6565
+ model,
6566
+ totalPredictions: totalAll,
6567
+ correctPredictions: correctAll,
6568
+ accuracy: totalAll > 0 ? correctAll / totalAll : 0,
6569
+ byDirection: byDir,
6570
+ period: `${days}d`
6571
+ };
6572
+ }
6573
+ // ---- Lifecycle -----------------------------------------------------------
6574
+ async close() {
6575
+ await this.pool.end();
6576
+ }
6577
+ };
6578
+ }
6579
+ });
6580
+
6581
+ // src/data/store-factory.ts
6582
+ async function getStore(config2) {
6583
+ if (instance) return instance;
6584
+ if (config2.database?.type === "postgres" && config2.database.url) {
6585
+ const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
6586
+ instance = new PostgresStore2(config2.database.url);
6587
+ } else {
6588
+ instance = new SqliteStore();
6589
+ }
6590
+ return instance;
6591
+ }
6592
+ function getStoreInstance() {
6593
+ return instance;
6594
+ }
6595
+ var instance;
6596
+ var init_store_factory = __esm({
6597
+ "src/data/store-factory.ts"() {
6598
+ "use strict";
6599
+ init_sqlite_store();
6600
+ instance = null;
6601
+ }
6602
+ });
6603
+
6022
6604
  // src/ai/tool-handler.ts
6023
6605
  async function handleTool(name, input) {
6024
6606
  const params = input;
@@ -6315,6 +6897,69 @@ async function handleTool(name, input) {
6315
6897
  disclaimer: prediction.disclaimer
6316
6898
  };
6317
6899
  }
6900
+ case "get_ml_prediction": {
6901
+ const symbol = String(params["symbol"] ?? "BTC");
6902
+ const mlClient2 = getMLClient();
6903
+ if (!mlClient2) {
6904
+ const prediction = await generatePrediction(symbol);
6905
+ return {
6906
+ ...prediction,
6907
+ mlAvailable: false,
6908
+ note: "ML sidecar not configured; using rule-based prediction"
6909
+ };
6910
+ }
6911
+ const features = await buildFeatureVector(symbol);
6912
+ const mlPred = await mlClient2.predict(features);
6913
+ if (!mlPred) {
6914
+ const prediction = await generatePrediction(symbol);
6915
+ return {
6916
+ ...prediction,
6917
+ mlAvailable: false,
6918
+ note: "ML sidecar unavailable; using rule-based prediction"
6919
+ };
6920
+ }
6921
+ return {
6922
+ symbol: mlPred.symbol,
6923
+ direction: mlPred.direction,
6924
+ probability: mlPred.probability,
6925
+ confidence: mlPred.confidence,
6926
+ model: mlPred.model,
6927
+ horizon: mlPred.horizon,
6928
+ mlAvailable: true,
6929
+ features: {
6930
+ rsi: features.rsi,
6931
+ macdHistogram: features.macdHistogram,
6932
+ bollingerPercentB: features.bollingerPercentB,
6933
+ fundingRate: features.fundingRate,
6934
+ fearGreed: features.fearGreed,
6935
+ rsiSlope: features.rsiSlope,
6936
+ volumeRatio: features.volumeRatio,
6937
+ emaCrossoverPct: features.emaCrossoverPct,
6938
+ atrPct: features.atrPct
6939
+ }
6940
+ };
6941
+ }
6942
+ case "get_model_accuracy": {
6943
+ const model = String(params["model"] ?? "lstm-predictor");
6944
+ const days = params["days"] ? Number(params["days"]) : 30;
6945
+ const store = getStoreInstance();
6946
+ if (!store) {
6947
+ return { error: "DataStore not initialized. ML accuracy requires PostgreSQL backend." };
6948
+ }
6949
+ const accuracy = await store.getPredictionAccuracy(model, days);
6950
+ return {
6951
+ model: accuracy.model,
6952
+ period: accuracy.period,
6953
+ totalPredictions: accuracy.totalPredictions,
6954
+ correctPredictions: accuracy.correctPredictions,
6955
+ accuracy: `${(accuracy.accuracy * 100).toFixed(1)}%`,
6956
+ byDirection: {
6957
+ up: `${(accuracy.byDirection.up.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.up.correct}/${accuracy.byDirection.up.total})`,
6958
+ down: `${(accuracy.byDirection.down.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.down.correct}/${accuracy.byDirection.down.total})`,
6959
+ sideways: `${(accuracy.byDirection.sideways.accuracy * 100).toFixed(1)}% (${accuracy.byDirection.sideways.correct}/${accuracy.byDirection.sideways.total})`
6960
+ }
6961
+ };
6962
+ }
6318
6963
  case "create_agent": {
6319
6964
  const agentName = String(params["name"] ?? "");
6320
6965
  const strategy = String(params["strategy"] ?? "momentum");
@@ -6392,6 +7037,9 @@ var init_tool_handler = __esm({
6392
7037
  init_technical_analysis();
6393
7038
  init_predictor();
6394
7039
  init_agent();
7040
+ init_client();
7041
+ init_feature_engineer();
7042
+ init_store_factory();
6395
7043
  }
6396
7044
  });
6397
7045
 
@@ -6649,6 +7297,38 @@ var init_tools = __esm({
6649
7297
  required: ["symbol"]
6650
7298
  }
6651
7299
  },
7300
+ {
7301
+ name: "get_ml_prediction",
7302
+ description: "Get an ML-enhanced prediction using LSTM/Random Forest models from the ML sidecar. Returns direction, probability, model confidence, and feature importance. Falls back to rule-based prediction if ML sidecar is unavailable.",
7303
+ input_schema: {
7304
+ type: "object",
7305
+ properties: {
7306
+ symbol: {
7307
+ type: "string",
7308
+ description: 'The token symbol (e.g. "BTC", "ETH", "SOL").'
7309
+ }
7310
+ },
7311
+ required: ["symbol"]
7312
+ }
7313
+ },
7314
+ {
7315
+ name: "get_model_accuracy",
7316
+ description: "Get historical accuracy metrics for ML prediction models. Shows total predictions, accuracy percentage, and breakdown by direction (up/down/sideways).",
7317
+ input_schema: {
7318
+ type: "object",
7319
+ properties: {
7320
+ model: {
7321
+ type: "string",
7322
+ description: 'Model name (e.g. "lstm-predictor", "signal-classifier"). Defaults to "lstm-predictor".'
7323
+ },
7324
+ days: {
7325
+ type: "number",
7326
+ description: "Number of days to look back for accuracy stats. Defaults to 30."
7327
+ }
7328
+ },
7329
+ required: []
7330
+ }
7331
+ },
6652
7332
  {
6653
7333
  name: "create_agent",
6654
7334
  description: "Create an autonomous trading agent that monitors crypto pairs using a strategy (momentum or trend-following). Returns the created agent config.",
@@ -7311,7 +7991,7 @@ var init_bot = __esm({
7311
7991
  "src/discord/bot.ts"() {
7312
7992
  "use strict";
7313
7993
  init_loader();
7314
- init_client();
7994
+ init_client2();
7315
7995
  init_tool_handler();
7316
7996
  init_tools();
7317
7997
  init_chat();
@@ -7993,7 +8673,7 @@ var init_bot2 = __esm({
7993
8673
  "src/telegram/bot.ts"() {
7994
8674
  "use strict";
7995
8675
  init_loader();
7996
- init_client();
8676
+ init_client2();
7997
8677
  init_tool_handler();
7998
8678
  init_tools();
7999
8679
  init_chat();
@@ -8048,40 +8728,18 @@ function handleBotValidate() {
8048
8728
  const config2 = getConfig();
8049
8729
  console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
8050
8730
  const checks = [
8051
- {
8052
- label: "Anthropic API Key",
8053
- value: config2.anthropicApiKey,
8054
- required: true,
8055
- key: "anthropicApiKey"
8056
- },
8057
- {
8058
- label: "Etherscan API Key",
8059
- value: config2.etherscanApiKey,
8060
- required: true,
8061
- key: "etherscanApiKey"
8062
- },
8063
- { label: "Discord Token", value: config2.discordToken, required: false, key: "discordToken" },
8064
- {
8065
- label: "Discord Guild ID",
8066
- value: config2.discordGuildId,
8067
- required: false,
8068
- key: "discordGuildId"
8069
- },
8070
- { label: "Telegram Token", value: config2.telegramToken, required: false, key: "telegramToken" },
8071
- {
8072
- label: "CryptoPanic Key",
8073
- value: config2.cryptopanicApiKey,
8074
- required: false,
8075
- key: "cryptopanicApiKey"
8076
- }
8731
+ { label: "Anthropic API Key", isSet: hasKey(config2.anthropicApiKey), required: true },
8732
+ { label: "Etherscan API Key", isSet: hasKey(config2.etherscanApiKey), required: true },
8733
+ { label: "Discord Token", isSet: hasKey(config2.discordToken), required: false },
8734
+ { label: "Discord Guild ID", isSet: hasKey(config2.discordGuildId), required: false },
8735
+ { label: "Telegram Token", isSet: hasKey(config2.telegramToken), required: false },
8736
+ { label: "CryptoPanic Key", isSet: hasKey(config2.cryptopanicApiKey), required: false }
8077
8737
  ];
8078
8738
  let allRequired = true;
8079
8739
  for (const check of checks) {
8080
- const set = hasKey(check.value);
8081
- const status = set ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
8082
- const masked = set ? maskKey(check.value) : chalk7.dim("(not set)");
8083
- console.log(` ${status.padEnd(18)} ${check.label.padEnd(20)} ${masked}`);
8084
- if (check.required && !set) allRequired = false;
8740
+ const status = check.isSet ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
8741
+ console.log(` ${status.padEnd(18)} ${check.label}`);
8742
+ if (check.required && !check.isSet) allRequired = false;
8085
8743
  }
8086
8744
  console.log();
8087
8745
  if (hasKey(config2.discordToken)) {
@@ -8114,6 +8772,186 @@ var init_bot3 = __esm({
8114
8772
  }
8115
8773
  });
8116
8774
 
8775
+ // src/data/collector.ts
8776
+ var log2, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
8777
+ var init_collector = __esm({
8778
+ "src/data/collector.ts"() {
8779
+ "use strict";
8780
+ init_binance();
8781
+ init_logger();
8782
+ log2 = createLogger("collector");
8783
+ MAJOR_SYMBOLS = [
8784
+ "BTC",
8785
+ "ETH",
8786
+ "SOL",
8787
+ "BNB",
8788
+ "XRP",
8789
+ "ADA",
8790
+ "DOGE",
8791
+ "DOT",
8792
+ "AVAX",
8793
+ "MATIC",
8794
+ "LINK",
8795
+ "UNI",
8796
+ "ATOM",
8797
+ "NEAR",
8798
+ "ARB",
8799
+ "OP",
8800
+ "SUI",
8801
+ "APT",
8802
+ "PEPE",
8803
+ "SHIB",
8804
+ "FLOKI",
8805
+ "BONK",
8806
+ "WIF"
8807
+ ];
8808
+ TIMEFRAMES = ["1h", "4h"];
8809
+ COLLECTION_INTERVAL_MS = 5 * 60 * 1e3;
8810
+ DataCollector = class {
8811
+ constructor(store, symbols = MAJOR_SYMBOLS, intervalMs = COLLECTION_INTERVAL_MS) {
8812
+ this.store = store;
8813
+ this.symbols = symbols;
8814
+ this.intervalMs = intervalMs;
8815
+ this.status.symbols = symbols;
8816
+ this.status.intervalMs = intervalMs;
8817
+ }
8818
+ timer = null;
8819
+ status = {
8820
+ running: false,
8821
+ symbols: MAJOR_SYMBOLS,
8822
+ timeframes: [...TIMEFRAMES],
8823
+ intervalMs: COLLECTION_INTERVAL_MS,
8824
+ lastRun: null,
8825
+ totalRecords: 0,
8826
+ errors: 0
8827
+ };
8828
+ getStatus() {
8829
+ return { ...this.status };
8830
+ }
8831
+ start() {
8832
+ if (this.status.running) {
8833
+ log2.warn("Collector already running");
8834
+ return;
8835
+ }
8836
+ log2.info(
8837
+ `Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
8838
+ );
8839
+ this.status.running = true;
8840
+ void this.collectAll();
8841
+ this.timer = setInterval(() => void this.collectAll(), this.intervalMs);
8842
+ }
8843
+ stop() {
8844
+ if (this.timer) {
8845
+ clearInterval(this.timer);
8846
+ this.timer = null;
8847
+ }
8848
+ this.status.running = false;
8849
+ log2.info("Data collector stopped");
8850
+ }
8851
+ async collectAll() {
8852
+ log2.info("Collection cycle starting");
8853
+ const start = Date.now();
8854
+ for (const timeframe of TIMEFRAMES) {
8855
+ for (const symbol of this.symbols) {
8856
+ try {
8857
+ await this.collectSymbol(symbol, timeframe);
8858
+ } catch (err) {
8859
+ this.status.errors++;
8860
+ log2.error(
8861
+ `Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
8862
+ );
8863
+ }
8864
+ }
8865
+ }
8866
+ this.status.lastRun = Date.now();
8867
+ const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
8868
+ log2.info(
8869
+ `Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
8870
+ );
8871
+ }
8872
+ async collectSymbol(symbol, timeframe) {
8873
+ const klines = await fetchKlines(symbol, timeframe, 100);
8874
+ const records = klines.map((k) => ({
8875
+ time: k.openTime,
8876
+ symbol: symbol.toUpperCase(),
8877
+ timeframe,
8878
+ open: k.open,
8879
+ high: k.high,
8880
+ low: k.low,
8881
+ close: k.close,
8882
+ volume: k.volume,
8883
+ trades: k.trades
8884
+ }));
8885
+ await this.store.insertOHLCV(records);
8886
+ this.status.totalRecords += records.length;
8887
+ }
8888
+ };
8889
+ }
8890
+ });
8891
+
8892
+ // src/cli/commands/collect.ts
8893
+ var collect_exports = {};
8894
+ __export(collect_exports, {
8895
+ handleCollectStart: () => handleCollectStart,
8896
+ handleCollectStatus: () => handleCollectStatus
8897
+ });
8898
+ import chalk8 from "chalk";
8899
+ async function handleCollectStart(options) {
8900
+ const config2 = getConfig();
8901
+ if (config2.database?.type !== "postgres" || !config2.database.url) {
8902
+ console.log(
8903
+ chalk8.yellow(
8904
+ 'Data collection requires PostgreSQL + TimescaleDB.\nSet database.type = "postgres" and database.url in your config.\n\nExample:\n vizzor config set database.type postgres\n vizzor config set database.url postgres://user:pass@localhost:5432/vizzor'
8905
+ )
8906
+ );
8907
+ return;
8908
+ }
8909
+ const store = await getStore(config2);
8910
+ const symbols = options.symbols ? options.symbols.split(",").map((s) => s.trim().toUpperCase()) : void 0;
8911
+ const intervalMs = options.interval ? options.interval * 1e3 : void 0;
8912
+ collector = new DataCollector(store, symbols, intervalMs);
8913
+ collector.start();
8914
+ console.log(chalk8.green("Data collector started"));
8915
+ const status = collector.getStatus();
8916
+ console.log(` Symbols: ${status.symbols.length} pairs`);
8917
+ console.log(` Timeframes: ${status.timeframes.join(", ")}`);
8918
+ console.log(` Interval: ${status.intervalMs / 1e3}s`);
8919
+ console.log(chalk8.dim("\nPress Ctrl+C to stop"));
8920
+ process.on("SIGINT", () => {
8921
+ collector?.stop();
8922
+ void store.close();
8923
+ process.exit(0);
8924
+ });
8925
+ await new Promise(() => {
8926
+ });
8927
+ }
8928
+ function handleCollectStatus() {
8929
+ if (!collector) {
8930
+ console.log(chalk8.yellow("No collector running in this process."));
8931
+ console.log(chalk8.dim("Start with: vizzor collect start"));
8932
+ return;
8933
+ }
8934
+ const status = collector.getStatus();
8935
+ console.log(chalk8.bold("Data Collector Status"));
8936
+ console.log(` Running: ${status.running ? chalk8.green("yes") : chalk8.red("no")}`);
8937
+ console.log(` Symbols: ${status.symbols.length} pairs`);
8938
+ console.log(` Timeframes: ${status.timeframes.join(", ")}`);
8939
+ console.log(` Interval: ${status.intervalMs / 1e3}s`);
8940
+ console.log(` Last run: ${status.lastRun ? new Date(status.lastRun).toISOString() : "never"}`);
8941
+ console.log(` Total records: ${status.totalRecords.toLocaleString()}`);
8942
+ console.log(` Errors: ${status.errors}`);
8943
+ }
8944
+ var collector;
8945
+ var init_collect = __esm({
8946
+ "src/cli/commands/collect.ts"() {
8947
+ "use strict";
8948
+ init_loader();
8949
+ init_store_factory();
8950
+ init_collector();
8951
+ collector = null;
8952
+ }
8953
+ });
8954
+
8117
8955
  // src/tui/components/status-bar.tsx
8118
8956
  import React, { useState, useEffect } from "react";
8119
8957
  import { Box, Text, Spacer } from "ink";
@@ -8854,7 +9692,7 @@ function useAIStream() {
8854
9692
  var init_use_ai_stream = __esm({
8855
9693
  "src/tui/hooks/use-ai-stream.ts"() {
8856
9694
  "use strict";
8857
- init_client();
9695
+ init_client2();
8858
9696
  init_chat();
8859
9697
  init_tools();
8860
9698
  init_context_injector();
@@ -9406,7 +10244,7 @@ var init_commands3 = __esm({
9406
10244
  init_constants();
9407
10245
  init_binance();
9408
10246
  init_agent();
9409
- init_client();
10247
+ init_client2();
9410
10248
  init_registry2();
9411
10249
  init_types();
9412
10250
  }
@@ -9848,7 +10686,7 @@ var init_app = __esm({
9848
10686
  init_commands3();
9849
10687
  init_loader();
9850
10688
  init_constants();
9851
- init_client();
10689
+ init_client2();
9852
10690
  init_tool_handler();
9853
10691
  init_tools();
9854
10692
  init_binance();
@@ -9858,12 +10696,12 @@ var init_app = __esm({
9858
10696
  // src/index.ts
9859
10697
  init_loader();
9860
10698
  import { Command } from "commander";
9861
- import { readFileSync as readFileSync2 } from "fs";
9862
- import { fileURLToPath } from "url";
9863
- import { dirname, resolve as resolve2 } from "path";
9864
- var __filename = fileURLToPath(import.meta.url);
9865
- var __dirname = dirname(__filename);
9866
- var pkg = JSON.parse(readFileSync2(resolve2(__dirname, "..", "package.json"), "utf-8"));
10699
+ import { readFileSync as readFileSync3 } from "fs";
10700
+ import { fileURLToPath as fileURLToPath2 } from "url";
10701
+ import { dirname as dirname2, resolve as resolve3 } from "path";
10702
+ var __filename2 = fileURLToPath2(import.meta.url);
10703
+ var __dirname2 = dirname2(__filename2);
10704
+ var pkg = JSON.parse(readFileSync3(resolve3(__dirname2, "..", "package.json"), "utf-8"));
9867
10705
  var program = new Command().name("vizzor").description("Crypto chronovisor \u2014 AI-powered on-chain intelligence").version(pkg.version);
9868
10706
  program.hook("preAction", async () => {
9869
10707
  await loadConfig();
@@ -9911,6 +10749,15 @@ botCmd.command("validate").description("Check bot token configuration").action(a
9911
10749
  const { handleBotValidate: handleBotValidate2 } = await Promise.resolve().then(() => (init_bot3(), bot_exports3));
9912
10750
  handleBotValidate2();
9913
10751
  });
10752
+ var collectCmd = program.command("collect").description("Data collection pipeline");
10753
+ collectCmd.command("start").description("Start background OHLCV data collection (requires PostgreSQL)").option("--symbols <symbols>", "Comma-separated symbols to collect (default: 23 major pairs)").option("--interval <seconds>", "Collection interval in seconds (default: 300)", parseInt).action(async (options) => {
10754
+ const { handleCollectStart: handleCollectStart2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
10755
+ await handleCollectStart2(options);
10756
+ });
10757
+ collectCmd.command("status").description("Show data collection status").action(async () => {
10758
+ const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
10759
+ handleCollectStatus2();
10760
+ });
9914
10761
  var args = process.argv.slice(2);
9915
10762
  if (args.length === 0) {
9916
10763
  await loadConfig();