@vizzor/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -59,6 +59,17 @@ var init_schema = __esm({
59
59
  sidecarUrl: "http://localhost:8000",
60
60
  fallbackToRules: true
61
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
+ })),
62
73
  discordToken: z.string().optional(),
63
74
  discordGuildId: z.string().optional(),
64
75
  telegramToken: z.string().optional()
@@ -656,12 +667,12 @@ var init_adapter = __esm({
656
667
  toBlock: options?.toBlock,
657
668
  args: options?.args
658
669
  });
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
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
665
676
  }));
666
677
  }
667
678
  // ── Tokens ──────────────────────────────────────────────────────────────
@@ -8952,6 +8963,481 @@ var init_collect = __esm({
8952
8963
  }
8953
8964
  });
8954
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
+
8955
9441
  // src/tui/components/status-bar.tsx
8956
9442
  import React, { useState, useEffect } from "react";
8957
9443
  import { Box, Text, Spacer } from "ink";
@@ -10127,8 +10613,8 @@ async function handleConfig(args2) {
10127
10613
  }
10128
10614
  const isSensitive = key.toLowerCase().includes("key") || key.toLowerCase().includes("token");
10129
10615
  if (isSensitive) {
10130
- const { validateKey: validateKey2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
10131
- const error = validateKey2(key, value);
10616
+ const { validateKey: validateKey3 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
10617
+ const error = validateKey3(key, value);
10132
10618
  if (error && !error.startsWith("Warning:")) {
10133
10619
  return { blocks: [], text: `Rejected: ${error}` };
10134
10620
  }
@@ -10758,6 +11244,24 @@ collectCmd.command("status").description("Show data collection status").action(a
10758
11244
  const { handleCollectStatus: handleCollectStatus2 } = await Promise.resolve().then(() => (init_collect(), collect_exports));
10759
11245
  handleCollectStatus2();
10760
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
+ });
10761
11265
  var args = process.argv.slice(2);
10762
11266
  if (args.length === 0) {
10763
11267
  await loadConfig();