@vizzor/cli 0.4.0 → 0.5.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,10 @@ 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" })),
49
53
  discordToken: z.string().optional(),
50
54
  discordGuildId: z.string().optional(),
51
55
  telegramToken: z.string().optional()
@@ -643,12 +647,12 @@ var init_adapter = __esm({
643
647
  toBlock: options?.toBlock,
644
648
  args: options?.args
645
649
  });
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
650
+ return logs.map((log2) => ({
651
+ eventName: log2.eventName ?? eventName,
652
+ blockNumber: log2.blockNumber ?? 0n,
653
+ transactionHash: log2.transactionHash ?? "0x",
654
+ args: log2.args ?? {},
655
+ logIndex: log2.logIndex ?? 0
652
656
  }));
653
657
  }
654
658
  // ── Tokens ──────────────────────────────────────────────────────────────
@@ -2561,7 +2565,17 @@ async function handleConfigSet(key, value) {
2561
2565
  }
2562
2566
  await loadConfig();
2563
2567
  const config2 = getConfig();
2564
- const updatedConfig = { ...config2, [key]: value };
2568
+ const updatedConfig = JSON.parse(JSON.stringify(config2));
2569
+ if (key.includes(".")) {
2570
+ const [section, field] = key.split(".");
2571
+ if (!updatedConfig[section] || typeof updatedConfig[section] !== "object") {
2572
+ updatedConfig[section] = {};
2573
+ }
2574
+ const parsed = field === "maxTokens" ? Number(value) : value;
2575
+ updatedConfig[section][field] = parsed;
2576
+ } else {
2577
+ updatedConfig[key] = value;
2578
+ }
2565
2579
  writeFileSync2(configPath, yamlStringify(updatedConfig), "utf-8");
2566
2580
  const displayValue = isSensitive ? maskKey(value) : value;
2567
2581
  console.log(chalk6.green(`Set ${key} = ${displayValue}`));
@@ -5514,6 +5528,26 @@ function getDb() {
5514
5528
  `);
5515
5529
  return db;
5516
5530
  }
5531
+ function getCached(key) {
5532
+ const row = getDb().prepare("SELECT value, expires_at FROM cache WHERE key = ?").get(key);
5533
+ if (!row) {
5534
+ return null;
5535
+ }
5536
+ const now = Math.floor(Date.now() / 1e3);
5537
+ if (row.expires_at <= now) {
5538
+ getDb().prepare("DELETE FROM cache WHERE key = ?").run(key);
5539
+ return null;
5540
+ }
5541
+ return JSON.parse(row.value);
5542
+ }
5543
+ function setCache(key, value, ttlSeconds) {
5544
+ const now = Math.floor(Date.now() / 1e3);
5545
+ const expiresAt = now + ttlSeconds;
5546
+ getDb().prepare(
5547
+ `INSERT OR REPLACE INTO cache (key, value, expires_at, created_at)
5548
+ VALUES (?, ?, ?, ?)`
5549
+ ).run(key, JSON.stringify(value), expiresAt, now);
5550
+ }
5517
5551
  var db;
5518
5552
  var init_cache = __esm({
5519
5553
  "src/data/cache.ts"() {
@@ -8048,40 +8082,18 @@ function handleBotValidate() {
8048
8082
  const config2 = getConfig();
8049
8083
  console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
8050
8084
  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
- }
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 }
8077
8091
  ];
8078
8092
  let allRequired = true;
8079
8093
  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;
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;
8085
8097
  }
8086
8098
  console.log();
8087
8099
  if (hasKey(config2.discordToken)) {
@@ -8114,6 +8126,613 @@ var init_bot3 = __esm({
8114
8126
  }
8115
8127
  });
8116
8128
 
8129
+ // src/data/sqlite-store.ts
8130
+ function ensureAgentTables2() {
8131
+ const db2 = getDb();
8132
+ db2.exec(`
8133
+ CREATE TABLE IF NOT EXISTS agents (
8134
+ id TEXT PRIMARY KEY,
8135
+ name TEXT NOT NULL UNIQUE,
8136
+ strategy TEXT NOT NULL,
8137
+ pairs TEXT NOT NULL,
8138
+ interval_seconds INTEGER NOT NULL DEFAULT 60,
8139
+ created_at INTEGER NOT NULL,
8140
+ updated_at INTEGER NOT NULL
8141
+ )
8142
+ `);
8143
+ db2.exec(`
8144
+ CREATE TABLE IF NOT EXISTS agent_decisions (
8145
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8146
+ agent_id TEXT NOT NULL,
8147
+ symbol TEXT NOT NULL,
8148
+ action TEXT NOT NULL,
8149
+ confidence INTEGER NOT NULL,
8150
+ reasoning TEXT NOT NULL,
8151
+ signals TEXT NOT NULL,
8152
+ created_at INTEGER NOT NULL,
8153
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
8154
+ )
8155
+ `);
8156
+ }
8157
+ function rowToConfig(row) {
8158
+ return {
8159
+ id: row.id,
8160
+ name: row.name,
8161
+ strategy: row.strategy,
8162
+ pairs: JSON.parse(row.pairs),
8163
+ interval: row.interval_seconds,
8164
+ createdAt: row.created_at,
8165
+ updatedAt: row.updated_at
8166
+ };
8167
+ }
8168
+ var SqliteStore;
8169
+ var init_sqlite_store = __esm({
8170
+ "src/data/sqlite-store.ts"() {
8171
+ "use strict";
8172
+ init_cache();
8173
+ SqliteStore = class {
8174
+ // ---- Cache ---------------------------------------------------------------
8175
+ async getCached(key) {
8176
+ return getCached(key);
8177
+ }
8178
+ async setCache(key, value, ttlSeconds) {
8179
+ setCache(key, value, ttlSeconds);
8180
+ }
8181
+ // ---- Agents --------------------------------------------------------------
8182
+ async createAgent(config2) {
8183
+ ensureAgentTables2();
8184
+ getDb().prepare(
8185
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
8186
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
8187
+ ).run(
8188
+ config2.id,
8189
+ config2.name,
8190
+ config2.strategy,
8191
+ JSON.stringify(config2.pairs),
8192
+ config2.interval,
8193
+ config2.createdAt,
8194
+ config2.updatedAt
8195
+ );
8196
+ return config2;
8197
+ }
8198
+ async listAgents() {
8199
+ ensureAgentTables2();
8200
+ const rows = getDb().prepare("SELECT * FROM agents ORDER BY created_at DESC").all();
8201
+ return rows.map(rowToConfig);
8202
+ }
8203
+ async getAgentById(id) {
8204
+ ensureAgentTables2();
8205
+ const row = getDb().prepare("SELECT * FROM agents WHERE id = ?").get(id);
8206
+ return row ? rowToConfig(row) : null;
8207
+ }
8208
+ async getAgentByName(name) {
8209
+ ensureAgentTables2();
8210
+ const row = getDb().prepare("SELECT * FROM agents WHERE name = ?").get(name);
8211
+ return row ? rowToConfig(row) : null;
8212
+ }
8213
+ async deleteAgent(id) {
8214
+ ensureAgentTables2();
8215
+ const result = getDb().prepare("DELETE FROM agents WHERE id = ?").run(id);
8216
+ getDb().prepare("DELETE FROM agent_decisions WHERE agent_id = ?").run(id);
8217
+ return result.changes > 0;
8218
+ }
8219
+ async logDecision(result) {
8220
+ ensureAgentTables2();
8221
+ getDb().prepare(
8222
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
8223
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
8224
+ ).run(
8225
+ result.agentId,
8226
+ result.symbol,
8227
+ result.decision.action,
8228
+ result.decision.confidence,
8229
+ JSON.stringify(result.decision.reasoning),
8230
+ JSON.stringify(result.signals),
8231
+ result.timestamp
8232
+ );
8233
+ }
8234
+ async getDecisions(agentId, limit) {
8235
+ ensureAgentTables2();
8236
+ const rows = getDb().prepare("SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?").all(agentId, limit);
8237
+ return rows.map((r) => ({
8238
+ agentId: r.agent_id,
8239
+ symbol: r.symbol,
8240
+ timestamp: r.created_at,
8241
+ signals: JSON.parse(r.signals),
8242
+ decision: {
8243
+ action: r.action,
8244
+ confidence: r.confidence,
8245
+ reasoning: JSON.parse(r.reasoning)
8246
+ }
8247
+ }));
8248
+ }
8249
+ // ---- Time-series (no-op for SQLite) --------------------------------------
8250
+ async insertOHLCV(_records) {
8251
+ }
8252
+ async queryOHLCV(_symbol, _timeframe, _from, _to) {
8253
+ return [];
8254
+ }
8255
+ // ---- Predictions (no-op for SQLite) --------------------------------------
8256
+ async logPrediction(_prediction) {
8257
+ }
8258
+ async getPredictionAccuracy(_model, _days) {
8259
+ return {
8260
+ model: _model,
8261
+ totalPredictions: 0,
8262
+ correctPredictions: 0,
8263
+ accuracy: 0,
8264
+ byDirection: {
8265
+ up: { total: 0, correct: 0, accuracy: 0 },
8266
+ down: { total: 0, correct: 0, accuracy: 0 },
8267
+ sideways: { total: 0, correct: 0, accuracy: 0 }
8268
+ },
8269
+ period: `${_days}d`
8270
+ };
8271
+ }
8272
+ // ---- Lifecycle -----------------------------------------------------------
8273
+ async close() {
8274
+ }
8275
+ };
8276
+ }
8277
+ });
8278
+
8279
+ // src/data/postgres-store.ts
8280
+ var postgres_store_exports = {};
8281
+ __export(postgres_store_exports, {
8282
+ PostgresStore: () => PostgresStore
8283
+ });
8284
+ import pg from "pg";
8285
+ import { readFileSync as readFileSync2 } from "fs";
8286
+ import { resolve as resolve2, dirname } from "path";
8287
+ import { fileURLToPath } from "url";
8288
+ var __filename, __dirname, PostgresStore;
8289
+ var init_postgres_store = __esm({
8290
+ "src/data/postgres-store.ts"() {
8291
+ "use strict";
8292
+ __filename = fileURLToPath(import.meta.url);
8293
+ __dirname = dirname(__filename);
8294
+ PostgresStore = class {
8295
+ pool;
8296
+ initialized = false;
8297
+ constructor(connectionUrl) {
8298
+ this.pool = new pg.Pool({ connectionString: connectionUrl, max: 10 });
8299
+ }
8300
+ async init() {
8301
+ if (this.initialized) return;
8302
+ const migrationPath = resolve2(__dirname, "migrations", "001-init.sql");
8303
+ const sql = readFileSync2(migrationPath, "utf-8");
8304
+ await this.pool.query(sql);
8305
+ this.initialized = true;
8306
+ }
8307
+ async query(text, params) {
8308
+ await this.init();
8309
+ return this.pool.query(text, params);
8310
+ }
8311
+ // ---- Cache ---------------------------------------------------------------
8312
+ async getCached(key) {
8313
+ const now = Math.floor(Date.now() / 1e3);
8314
+ const { rows } = await this.query(
8315
+ "SELECT value FROM cache WHERE key = $1 AND expires_at > $2",
8316
+ [key, now]
8317
+ );
8318
+ if (rows.length === 0) return null;
8319
+ return rows[0].value;
8320
+ }
8321
+ async setCache(key, value, ttlSeconds) {
8322
+ const now = Math.floor(Date.now() / 1e3);
8323
+ const expiresAt = now + ttlSeconds;
8324
+ await this.query(
8325
+ `INSERT INTO cache (key, value, expires_at, created_at)
8326
+ VALUES ($1, $2, $3, $4)
8327
+ ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
8328
+ [key, JSON.stringify(value), expiresAt, now]
8329
+ );
8330
+ }
8331
+ // ---- Agents --------------------------------------------------------------
8332
+ async createAgent(config2) {
8333
+ await this.query(
8334
+ `INSERT INTO agents (id, name, strategy, pairs, interval_seconds, created_at, updated_at)
8335
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
8336
+ [
8337
+ config2.id,
8338
+ config2.name,
8339
+ config2.strategy,
8340
+ JSON.stringify(config2.pairs),
8341
+ config2.interval,
8342
+ config2.createdAt,
8343
+ config2.updatedAt
8344
+ ]
8345
+ );
8346
+ return config2;
8347
+ }
8348
+ async listAgents() {
8349
+ const { rows } = await this.query("SELECT * FROM agents ORDER BY created_at DESC");
8350
+ return rows.map((r) => ({
8351
+ id: r.id,
8352
+ name: r.name,
8353
+ strategy: r.strategy,
8354
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
8355
+ interval: r.interval_seconds,
8356
+ createdAt: Number(r.created_at),
8357
+ updatedAt: Number(r.updated_at)
8358
+ }));
8359
+ }
8360
+ async getAgentById(id) {
8361
+ const { rows } = await this.query("SELECT * FROM agents WHERE id = $1", [id]);
8362
+ if (rows.length === 0) return null;
8363
+ const r = rows[0];
8364
+ return {
8365
+ id: r.id,
8366
+ name: r.name,
8367
+ strategy: r.strategy,
8368
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
8369
+ interval: r.interval_seconds,
8370
+ createdAt: Number(r.created_at),
8371
+ updatedAt: Number(r.updated_at)
8372
+ };
8373
+ }
8374
+ async getAgentByName(name) {
8375
+ const { rows } = await this.query("SELECT * FROM agents WHERE name = $1", [name]);
8376
+ if (rows.length === 0) return null;
8377
+ const r = rows[0];
8378
+ return {
8379
+ id: r.id,
8380
+ name: r.name,
8381
+ strategy: r.strategy,
8382
+ pairs: typeof r.pairs === "string" ? JSON.parse(r.pairs) : r.pairs,
8383
+ interval: r.interval_seconds,
8384
+ createdAt: Number(r.created_at),
8385
+ updatedAt: Number(r.updated_at)
8386
+ };
8387
+ }
8388
+ async deleteAgent(id) {
8389
+ const result = await this.query("DELETE FROM agents WHERE id = $1", [id]);
8390
+ return (result.rowCount ?? 0) > 0;
8391
+ }
8392
+ async logDecision(result) {
8393
+ await this.query(
8394
+ `INSERT INTO agent_decisions (agent_id, symbol, action, confidence, reasoning, signals, created_at)
8395
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
8396
+ [
8397
+ result.agentId,
8398
+ result.symbol,
8399
+ result.decision.action,
8400
+ result.decision.confidence,
8401
+ JSON.stringify(result.decision.reasoning),
8402
+ JSON.stringify(result.signals),
8403
+ result.timestamp
8404
+ ]
8405
+ );
8406
+ }
8407
+ async getDecisions(agentId, limit) {
8408
+ const { rows } = await this.query("SELECT * FROM agent_decisions WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2", [
8409
+ agentId,
8410
+ limit
8411
+ ]);
8412
+ return rows.map((r) => ({
8413
+ agentId: r.agent_id,
8414
+ symbol: r.symbol,
8415
+ timestamp: Number(r.created_at),
8416
+ signals: typeof r.signals === "string" ? JSON.parse(r.signals) : r.signals,
8417
+ decision: {
8418
+ action: r.action,
8419
+ confidence: r.confidence,
8420
+ reasoning: typeof r.reasoning === "string" ? JSON.parse(r.reasoning) : r.reasoning
8421
+ }
8422
+ }));
8423
+ }
8424
+ // ---- Time-series ---------------------------------------------------------
8425
+ async insertOHLCV(records) {
8426
+ if (records.length === 0) return;
8427
+ const values = [];
8428
+ const placeholders = [];
8429
+ for (let i = 0; i < records.length; i++) {
8430
+ const r = records[i];
8431
+ const offset = i * 9;
8432
+ placeholders.push(
8433
+ `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9})`
8434
+ );
8435
+ values.push(
8436
+ new Date(r.time).toISOString(),
8437
+ r.symbol,
8438
+ r.timeframe,
8439
+ r.open,
8440
+ r.high,
8441
+ r.low,
8442
+ r.close,
8443
+ r.volume,
8444
+ r.trades
8445
+ );
8446
+ }
8447
+ await this.query(
8448
+ `INSERT INTO ohlcv (time, symbol, timeframe, open, high, low, close, volume, trades)
8449
+ VALUES ${placeholders.join(", ")}
8450
+ ON CONFLICT (symbol, timeframe, time) DO UPDATE
8451
+ SET open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low,
8452
+ close = EXCLUDED.close, volume = EXCLUDED.volume, trades = EXCLUDED.trades`,
8453
+ values
8454
+ );
8455
+ }
8456
+ async queryOHLCV(symbol, timeframe, from, to) {
8457
+ const { rows } = await this.query(
8458
+ `SELECT * FROM ohlcv
8459
+ WHERE symbol = $1 AND timeframe = $2 AND time >= $3 AND time <= $4
8460
+ ORDER BY time ASC`,
8461
+ [symbol, timeframe, new Date(from).toISOString(), new Date(to).toISOString()]
8462
+ );
8463
+ return rows.map((r) => ({
8464
+ time: new Date(r.time).getTime(),
8465
+ symbol: r.symbol,
8466
+ timeframe: r.timeframe,
8467
+ open: r.open,
8468
+ high: r.high,
8469
+ low: r.low,
8470
+ close: r.close,
8471
+ volume: r.volume,
8472
+ trades: r.trades
8473
+ }));
8474
+ }
8475
+ // ---- Predictions ---------------------------------------------------------
8476
+ async logPrediction(prediction) {
8477
+ await this.query(
8478
+ `INSERT INTO predictions (symbol, model, direction, probability, horizon, features, predicted_at)
8479
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
8480
+ [
8481
+ prediction.symbol,
8482
+ prediction.model,
8483
+ prediction.direction,
8484
+ prediction.probability,
8485
+ prediction.horizon,
8486
+ JSON.stringify(prediction.features),
8487
+ new Date(prediction.predictedAt).toISOString()
8488
+ ]
8489
+ );
8490
+ }
8491
+ async getPredictionAccuracy(model, days) {
8492
+ const since = new Date(Date.now() - days * 864e5).toISOString();
8493
+ const { rows: totals } = await this.query(
8494
+ `SELECT direction,
8495
+ COUNT(*)::text AS total,
8496
+ COUNT(*) FILTER (WHERE was_correct = true)::text AS correct
8497
+ FROM predictions
8498
+ WHERE model = $1 AND predicted_at >= $2 AND was_correct IS NOT NULL
8499
+ GROUP BY direction`,
8500
+ [model, since]
8501
+ );
8502
+ const byDir = {
8503
+ up: { total: 0, correct: 0, accuracy: 0 },
8504
+ down: { total: 0, correct: 0, accuracy: 0 },
8505
+ sideways: { total: 0, correct: 0, accuracy: 0 }
8506
+ };
8507
+ let totalAll = 0;
8508
+ let correctAll = 0;
8509
+ for (const row of totals) {
8510
+ const t = parseInt(row.total, 10);
8511
+ const c = parseInt(row.correct, 10);
8512
+ totalAll += t;
8513
+ correctAll += c;
8514
+ const dir = row.direction;
8515
+ if (byDir[dir]) {
8516
+ byDir[dir] = { total: t, correct: c, accuracy: t > 0 ? c / t : 0 };
8517
+ }
8518
+ }
8519
+ return {
8520
+ model,
8521
+ totalPredictions: totalAll,
8522
+ correctPredictions: correctAll,
8523
+ accuracy: totalAll > 0 ? correctAll / totalAll : 0,
8524
+ byDirection: byDir,
8525
+ period: `${days}d`
8526
+ };
8527
+ }
8528
+ // ---- Lifecycle -----------------------------------------------------------
8529
+ async close() {
8530
+ await this.pool.end();
8531
+ }
8532
+ };
8533
+ }
8534
+ });
8535
+
8536
+ // src/data/store-factory.ts
8537
+ async function getStore(config2) {
8538
+ if (instance) return instance;
8539
+ if (config2.database?.type === "postgres" && config2.database.url) {
8540
+ const { PostgresStore: PostgresStore2 } = await Promise.resolve().then(() => (init_postgres_store(), postgres_store_exports));
8541
+ instance = new PostgresStore2(config2.database.url);
8542
+ } else {
8543
+ instance = new SqliteStore();
8544
+ }
8545
+ return instance;
8546
+ }
8547
+ var instance;
8548
+ var init_store_factory = __esm({
8549
+ "src/data/store-factory.ts"() {
8550
+ "use strict";
8551
+ init_sqlite_store();
8552
+ instance = null;
8553
+ }
8554
+ });
8555
+
8556
+ // src/data/collector.ts
8557
+ var log, MAJOR_SYMBOLS, TIMEFRAMES, COLLECTION_INTERVAL_MS, DataCollector;
8558
+ var init_collector = __esm({
8559
+ "src/data/collector.ts"() {
8560
+ "use strict";
8561
+ init_binance();
8562
+ init_logger();
8563
+ log = createLogger("collector");
8564
+ MAJOR_SYMBOLS = [
8565
+ "BTC",
8566
+ "ETH",
8567
+ "SOL",
8568
+ "BNB",
8569
+ "XRP",
8570
+ "ADA",
8571
+ "DOGE",
8572
+ "DOT",
8573
+ "AVAX",
8574
+ "MATIC",
8575
+ "LINK",
8576
+ "UNI",
8577
+ "ATOM",
8578
+ "NEAR",
8579
+ "ARB",
8580
+ "OP",
8581
+ "SUI",
8582
+ "APT",
8583
+ "PEPE",
8584
+ "SHIB",
8585
+ "FLOKI",
8586
+ "BONK",
8587
+ "WIF"
8588
+ ];
8589
+ TIMEFRAMES = ["1h", "4h"];
8590
+ COLLECTION_INTERVAL_MS = 5 * 60 * 1e3;
8591
+ DataCollector = class {
8592
+ constructor(store, symbols = MAJOR_SYMBOLS, intervalMs = COLLECTION_INTERVAL_MS) {
8593
+ this.store = store;
8594
+ this.symbols = symbols;
8595
+ this.intervalMs = intervalMs;
8596
+ this.status.symbols = symbols;
8597
+ this.status.intervalMs = intervalMs;
8598
+ }
8599
+ timer = null;
8600
+ status = {
8601
+ running: false,
8602
+ symbols: MAJOR_SYMBOLS,
8603
+ timeframes: [...TIMEFRAMES],
8604
+ intervalMs: COLLECTION_INTERVAL_MS,
8605
+ lastRun: null,
8606
+ totalRecords: 0,
8607
+ errors: 0
8608
+ };
8609
+ getStatus() {
8610
+ return { ...this.status };
8611
+ }
8612
+ start() {
8613
+ if (this.status.running) {
8614
+ log.warn("Collector already running");
8615
+ return;
8616
+ }
8617
+ log.info(
8618
+ `Starting data collector: ${this.symbols.length} symbols, ${TIMEFRAMES.length} timeframes, every ${this.intervalMs / 1e3}s`
8619
+ );
8620
+ this.status.running = true;
8621
+ void this.collectAll();
8622
+ this.timer = setInterval(() => void this.collectAll(), this.intervalMs);
8623
+ }
8624
+ stop() {
8625
+ if (this.timer) {
8626
+ clearInterval(this.timer);
8627
+ this.timer = null;
8628
+ }
8629
+ this.status.running = false;
8630
+ log.info("Data collector stopped");
8631
+ }
8632
+ async collectAll() {
8633
+ log.info("Collection cycle starting");
8634
+ const start = Date.now();
8635
+ for (const timeframe of TIMEFRAMES) {
8636
+ for (const symbol of this.symbols) {
8637
+ try {
8638
+ await this.collectSymbol(symbol, timeframe);
8639
+ } catch (err) {
8640
+ this.status.errors++;
8641
+ log.error(
8642
+ `Failed to collect ${symbol}/${timeframe}: ${err instanceof Error ? err.message : String(err)}`
8643
+ );
8644
+ }
8645
+ }
8646
+ }
8647
+ this.status.lastRun = Date.now();
8648
+ const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
8649
+ log.info(
8650
+ `Collection cycle complete in ${elapsed}s (total records: ${this.status.totalRecords})`
8651
+ );
8652
+ }
8653
+ async collectSymbol(symbol, timeframe) {
8654
+ const klines = await fetchKlines(symbol, timeframe, 100);
8655
+ const records = klines.map((k) => ({
8656
+ time: k.openTime,
8657
+ symbol: symbol.toUpperCase(),
8658
+ timeframe,
8659
+ open: k.open,
8660
+ high: k.high,
8661
+ low: k.low,
8662
+ close: k.close,
8663
+ volume: k.volume,
8664
+ trades: k.trades
8665
+ }));
8666
+ await this.store.insertOHLCV(records);
8667
+ this.status.totalRecords += records.length;
8668
+ }
8669
+ };
8670
+ }
8671
+ });
8672
+
8673
+ // src/cli/commands/collect.ts
8674
+ var collect_exports = {};
8675
+ __export(collect_exports, {
8676
+ handleCollectStart: () => handleCollectStart,
8677
+ handleCollectStatus: () => handleCollectStatus
8678
+ });
8679
+ import chalk8 from "chalk";
8680
+ async function handleCollectStart(options) {
8681
+ const config2 = getConfig();
8682
+ if (config2.database?.type !== "postgres" || !config2.database.url) {
8683
+ console.log(
8684
+ chalk8.yellow(
8685
+ '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'
8686
+ )
8687
+ );
8688
+ return;
8689
+ }
8690
+ const store = await getStore(config2);
8691
+ const symbols = options.symbols ? options.symbols.split(",").map((s) => s.trim().toUpperCase()) : void 0;
8692
+ const intervalMs = options.interval ? options.interval * 1e3 : void 0;
8693
+ collector = new DataCollector(store, symbols, intervalMs);
8694
+ collector.start();
8695
+ console.log(chalk8.green("Data collector started"));
8696
+ const status = collector.getStatus();
8697
+ console.log(` Symbols: ${status.symbols.length} pairs`);
8698
+ console.log(` Timeframes: ${status.timeframes.join(", ")}`);
8699
+ console.log(` Interval: ${status.intervalMs / 1e3}s`);
8700
+ console.log(chalk8.dim("\nPress Ctrl+C to stop"));
8701
+ process.on("SIGINT", () => {
8702
+ collector?.stop();
8703
+ void store.close();
8704
+ process.exit(0);
8705
+ });
8706
+ await new Promise(() => {
8707
+ });
8708
+ }
8709
+ function handleCollectStatus() {
8710
+ if (!collector) {
8711
+ console.log(chalk8.yellow("No collector running in this process."));
8712
+ console.log(chalk8.dim("Start with: vizzor collect start"));
8713
+ return;
8714
+ }
8715
+ const status = collector.getStatus();
8716
+ console.log(chalk8.bold("Data Collector Status"));
8717
+ console.log(` Running: ${status.running ? chalk8.green("yes") : chalk8.red("no")}`);
8718
+ console.log(` Symbols: ${status.symbols.length} pairs`);
8719
+ console.log(` Timeframes: ${status.timeframes.join(", ")}`);
8720
+ console.log(` Interval: ${status.intervalMs / 1e3}s`);
8721
+ console.log(` Last run: ${status.lastRun ? new Date(status.lastRun).toISOString() : "never"}`);
8722
+ console.log(` Total records: ${status.totalRecords.toLocaleString()}`);
8723
+ console.log(` Errors: ${status.errors}`);
8724
+ }
8725
+ var collector;
8726
+ var init_collect = __esm({
8727
+ "src/cli/commands/collect.ts"() {
8728
+ "use strict";
8729
+ init_loader();
8730
+ init_store_factory();
8731
+ init_collector();
8732
+ collector = null;
8733
+ }
8734
+ });
8735
+
8117
8736
  // src/tui/components/status-bar.tsx
8118
8737
  import React, { useState, useEffect } from "react";
8119
8738
  import { Box, Text, Spacer } from "ink";
@@ -9858,12 +10477,12 @@ var init_app = __esm({
9858
10477
  // src/index.ts
9859
10478
  init_loader();
9860
10479
  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"));
10480
+ import { readFileSync as readFileSync3 } from "fs";
10481
+ import { fileURLToPath as fileURLToPath2 } from "url";
10482
+ import { dirname as dirname2, resolve as resolve3 } from "path";
10483
+ var __filename2 = fileURLToPath2(import.meta.url);
10484
+ var __dirname2 = dirname2(__filename2);
10485
+ var pkg = JSON.parse(readFileSync3(resolve3(__dirname2, "..", "package.json"), "utf-8"));
9867
10486
  var program = new Command().name("vizzor").description("Crypto chronovisor \u2014 AI-powered on-chain intelligence").version(pkg.version);
9868
10487
  program.hook("preAction", async () => {
9869
10488
  await loadConfig();
@@ -9911,6 +10530,15 @@ botCmd.command("validate").description("Check bot token configuration").action(a
9911
10530
  const { handleBotValidate: handleBotValidate2 } = await Promise.resolve().then(() => (init_bot3(), bot_exports3));
9912
10531
  handleBotValidate2();
9913
10532
  });
10533
+ var collectCmd = program.command("collect").description("Data collection pipeline");
10534
+ 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) => {
10535
+ const { handleCollectStart: handleCollectStart2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
10536
+ await handleCollectStart2(options);
10537
+ });
10538
+ collectCmd.command("status").description("Show data collection status").action(async () => {
10539
+ const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
10540
+ handleCollectStatus2();
10541
+ });
9914
10542
  var args = process.argv.slice(2);
9915
10543
  if (args.length === 0) {
9916
10544
  await loadConfig();