@vizzor/cli 0.3.4 → 0.3.5

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/README.md CHANGED
@@ -205,6 +205,7 @@ vizzor config init # Initialize config
205
205
  vizzor config set <key> <value> # Set config value
206
206
  vizzor config show # Show config
207
207
  vizzor bot start [options] # Start Discord/Telegram bots
208
+ vizzor bot validate # Check bot token configuration
208
209
  ```
209
210
 
210
211
  ### TUI Slash Commands
@@ -236,6 +237,20 @@ vizzor bot start [options] # Start Discord/Telegram bots
236
237
 
237
238
  **Price Ticker:** Arrow keys to navigate, **Enter** to trigger full AI prediction for any token, **Tab** to toggle focus.
238
239
 
240
+ ### Discord Bot
241
+
242
+ | Command | Description |
243
+ |---------|-------------|
244
+ | `/scan <address>` | Token security + risk scan |
245
+ | `/trends` | Trending tokens + market data |
246
+ | `/track <wallet>` | Wallet forensics |
247
+ | `/ico` | Upcoming launches and rounds |
248
+ | `/audit <contract>` | Contract audit |
249
+ | `/help` | Show all commands |
250
+ | *@mention* | AI chat guidance |
251
+
252
+ **Setup**: Enable the `MESSAGE_CONTENT` privileged intent in the [Discord Developer Portal](https://discord.com/developers/applications) for @mention responses.
253
+
239
254
  ### Telegram Bot
240
255
 
241
256
  | Command | Description |
package/dist/index.js CHANGED
@@ -2598,6 +2598,40 @@ var init_config = __esm({
2598
2598
  }
2599
2599
  });
2600
2600
 
2601
+ // src/discord/middleware/rate-limit.ts
2602
+ function checkRateLimit(userId) {
2603
+ const now = Date.now();
2604
+ const entry = userLimits.get(userId);
2605
+ if (!entry || now >= entry.resetAt) {
2606
+ userLimits.set(userId, { count: 1, resetAt: now + WINDOW_MS });
2607
+ return { allowed: true };
2608
+ }
2609
+ if (entry.count >= MAX_REQUESTS) {
2610
+ return { allowed: false };
2611
+ }
2612
+ entry.count++;
2613
+ return { allowed: true };
2614
+ }
2615
+ function startRateLimitCleanup(intervalMs = 3e5) {
2616
+ return setInterval(() => {
2617
+ const now = Date.now();
2618
+ for (const [userId, entry] of userLimits) {
2619
+ if (now >= entry.resetAt) {
2620
+ userLimits.delete(userId);
2621
+ }
2622
+ }
2623
+ }, intervalMs);
2624
+ }
2625
+ var userLimits, MAX_REQUESTS, WINDOW_MS;
2626
+ var init_rate_limit = __esm({
2627
+ "src/discord/middleware/rate-limit.ts"() {
2628
+ "use strict";
2629
+ userLimits = /* @__PURE__ */ new Map();
2630
+ MAX_REQUESTS = 10;
2631
+ WINDOW_MS = 6e4;
2632
+ }
2633
+ });
2634
+
2601
2635
  // src/discord/commands/index.ts
2602
2636
  import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
2603
2637
  function registerSlashCommands() {
@@ -2607,30 +2641,54 @@ function registerSlashCommands() {
2607
2641
  ).addStringOption(
2608
2642
  (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
2609
2643
  ).toJSON(),
2610
- new SlashCommandBuilder().setName("trends").setDescription("View market trends").toJSON(),
2644
+ new SlashCommandBuilder().setName("trends").setDescription("Trending tokens + market data").toJSON(),
2611
2645
  new SlashCommandBuilder().setName("track").setDescription("Analyze a wallet").addStringOption(
2612
2646
  (opt) => opt.setName("wallet").setDescription("Wallet address").setRequired(true)
2613
2647
  ).addStringOption(
2614
2648
  (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
2615
2649
  ).toJSON(),
2616
- new SlashCommandBuilder().setName("ico").setDescription("List upcoming ICOs/IDOs").toJSON(),
2650
+ new SlashCommandBuilder().setName("ico").setDescription("Upcoming ICOs & fundraising rounds").toJSON(),
2617
2651
  new SlashCommandBuilder().setName("audit").setDescription("Audit a smart contract").addStringOption(
2618
2652
  (opt) => opt.setName("contract").setDescription("Contract address").setRequired(true)
2619
2653
  ).addStringOption(
2620
2654
  (opt) => opt.setName("chain").setDescription("Target chain").setRequired(false)
2621
- ).toJSON()
2655
+ ).toJSON(),
2656
+ new SlashCommandBuilder().setName("help").setDescription("Show all Vizzor commands").toJSON()
2622
2657
  ];
2623
2658
  }
2624
2659
  async function handleSlashCommand(interaction) {
2625
2660
  const { commandName } = interaction;
2661
+ const { allowed } = checkRateLimit(interaction.user.id);
2662
+ if (!allowed) {
2663
+ await interaction.reply({
2664
+ content: "Rate limited. Please wait a moment before sending more commands.",
2665
+ ephemeral: true
2666
+ });
2667
+ return;
2668
+ }
2626
2669
  try {
2627
2670
  switch (commandName) {
2628
2671
  case "scan":
2629
2672
  await handleScanCommand(interaction);
2630
2673
  break;
2674
+ case "trends":
2675
+ await handleTrendsCommand(interaction);
2676
+ break;
2677
+ case "track":
2678
+ await handleTrackCommand(interaction);
2679
+ break;
2680
+ case "ico":
2681
+ await handleIcoCommand(interaction);
2682
+ break;
2683
+ case "audit":
2684
+ await handleAuditCommand(interaction);
2685
+ break;
2686
+ case "help":
2687
+ await handleHelpCommand(interaction);
2688
+ break;
2631
2689
  default:
2632
2690
  await interaction.reply({
2633
- content: `Command \`/${commandName}\` is coming soon.`,
2691
+ content: `Unknown command: \`/${commandName}\`. Use \`/help\` for available commands.`,
2634
2692
  ephemeral: true
2635
2693
  });
2636
2694
  }
@@ -2678,6 +2736,133 @@ async function handleScanCommand(interaction) {
2678
2736
  }
2679
2737
  await interaction.editReply({ embeds: [embed] });
2680
2738
  }
2739
+ async function handleTrendsCommand(interaction) {
2740
+ await interaction.deferReply();
2741
+ const trending = await fetchTrendingTokens();
2742
+ if (trending.length === 0) {
2743
+ await interaction.editReply("No trending data available right now.");
2744
+ return;
2745
+ }
2746
+ const embed = new EmbedBuilder().setTitle("Trending Tokens").setColor(5793266).setFooter({ text: "Live data from DexScreener & CoinGecko" }).setTimestamp();
2747
+ for (const t of trending.slice(0, 10)) {
2748
+ const changeSign = t.priceChange24h >= 0 ? "+" : "";
2749
+ const vol = t.volume24h >= 1e6 ? `$${(t.volume24h / 1e6).toFixed(1)}M` : `$${Math.round(t.volume24h).toLocaleString()}`;
2750
+ embed.addFields({
2751
+ name: `${t.symbol} (${t.chain})`,
2752
+ value: `Price: $${t.priceUsd}
2753
+ 24h: ${changeSign}${t.priceChange24h.toFixed(1)}%
2754
+ Vol: ${vol}
2755
+ Source: ${t.source}`,
2756
+ inline: true
2757
+ });
2758
+ }
2759
+ await interaction.editReply({ embeds: [embed] });
2760
+ }
2761
+ async function handleTrackCommand(interaction) {
2762
+ await interaction.deferReply();
2763
+ const wallet = interaction.options.getString("wallet", true);
2764
+ const chain = interaction.options.getString("chain") ?? "ethereum";
2765
+ const adapter = getAdapter(chain);
2766
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
2767
+ const analysis = await analyzeWallet(wallet, adapter);
2768
+ await adapter.disconnect();
2769
+ const riskColor = analysis.riskLevel === "clean" ? 65280 : analysis.riskLevel === "suspicious" ? 16776960 : 16711680;
2770
+ const embed = new EmbedBuilder().setTitle(`Wallet Analysis`).setColor(riskColor).addFields(
2771
+ { name: "Address", value: `\`${wallet}\``, inline: false },
2772
+ { name: "Chain", value: chain, inline: true },
2773
+ { name: "Balance", value: `${analysis.balance.toString()} wei`, inline: true },
2774
+ { name: "Transactions", value: String(analysis.transactionCount), inline: true },
2775
+ { name: "Risk Level", value: analysis.riskLevel.toUpperCase(), inline: true }
2776
+ ).setFooter({ text: "Vizzor by 7ayLabs \u2014 Not financial advice" }).setTimestamp();
2777
+ if (analysis.patterns.length > 0) {
2778
+ const patternText = analysis.patterns.map((p) => `[${p.severity.toUpperCase()}] ${p.description}`).join("\n");
2779
+ embed.addFields({ name: "Patterns", value: patternText });
2780
+ } else {
2781
+ embed.addFields({ name: "Patterns", value: "No unusual patterns detected." });
2782
+ }
2783
+ await interaction.editReply({ embeds: [embed] });
2784
+ }
2785
+ async function handleIcoCommand(interaction) {
2786
+ await interaction.deferReply();
2787
+ const [icosResult, raisesResult] = await Promise.allSettled([
2788
+ fetchUpcomingICOs(),
2789
+ fetchRecentRaises(30)
2790
+ ]);
2791
+ const icos = icosResult.status === "fulfilled" ? icosResult.value : [];
2792
+ const raises = raisesResult.status === "fulfilled" ? raisesResult.value : [];
2793
+ const items = raises.slice(0, 10).map((r) => ({
2794
+ name: r.name,
2795
+ round: r.round,
2796
+ amount: r.amount,
2797
+ chains: r.chains,
2798
+ leadInvestors: r.leadInvestors,
2799
+ date: new Date(r.date * 1e3).toISOString().split("T")[0] ?? ""
2800
+ }));
2801
+ const raiseNames = new Set(items.map((i) => i.name.toLowerCase()));
2802
+ for (const ico of icos.slice(0, 5)) {
2803
+ if (!raiseNames.has(ico.name.toLowerCase())) {
2804
+ items.push({
2805
+ name: ico.name,
2806
+ round: ico.status,
2807
+ amount: null,
2808
+ chains: [ico.chain ?? "multi-chain"],
2809
+ leadInvestors: [],
2810
+ date: ico.startDate ?? ""
2811
+ });
2812
+ }
2813
+ }
2814
+ if (items.length === 0) {
2815
+ await interaction.editReply("No ICO or fundraising data available right now.");
2816
+ return;
2817
+ }
2818
+ const embed = new EmbedBuilder().setTitle("Upcoming ICOs & Fundraising Rounds").setColor(5793266).setFooter({ text: "Data from DeFiLlama & Pump.fun" }).setTimestamp();
2819
+ for (const item of items.slice(0, 10)) {
2820
+ const amount = item.amount ? `$${(item.amount / 1e6).toFixed(1)}M` : "Undisclosed";
2821
+ const chains = item.chains.join(", ") || "multi-chain";
2822
+ let value = `${item.round} (${amount})
2823
+ ${chains} | ${item.date}`;
2824
+ if (item.leadInvestors.length > 0) {
2825
+ value += `
2826
+ Led by: ${item.leadInvestors.slice(0, 3).join(", ")}`;
2827
+ }
2828
+ embed.addFields({ name: item.name, value, inline: false });
2829
+ }
2830
+ await interaction.editReply({ embeds: [embed] });
2831
+ }
2832
+ async function handleAuditCommand(interaction) {
2833
+ await interaction.deferReply();
2834
+ const contract = interaction.options.getString("contract", true);
2835
+ const chain = interaction.options.getString("chain") ?? "ethereum";
2836
+ const adapter = getAdapter(chain);
2837
+ await adapter.connect(void 0, getConfig().etherscanApiKey);
2838
+ const result = await auditContract(contract, adapter);
2839
+ await adapter.disconnect();
2840
+ const riskColor = result.overallRisk === "low" ? 65280 : result.overallRisk === "medium" ? 16776960 : result.overallRisk === "high" ? 16746496 : 16711680;
2841
+ const embed = new EmbedBuilder().setTitle(`Contract Audit`).setColor(riskColor).addFields(
2842
+ { name: "Address", value: `\`${contract}\``, inline: false },
2843
+ { name: "Chain", value: chain, inline: true },
2844
+ { name: "Risk Level", value: result.overallRisk.toUpperCase(), inline: true },
2845
+ { name: "Has Code", value: result.hasCode ? "Yes" : "No", inline: true },
2846
+ { name: "Code Size", value: `${result.codeSize} bytes`, inline: true }
2847
+ ).setFooter({ text: "Vizzor by 7ayLabs \u2014 Not financial advice" }).setTimestamp();
2848
+ if (result.findings.length > 0) {
2849
+ const findingsText = result.findings.map((f) => `[${f.severity.toUpperCase()}] ${f.title}: ${f.description}`).join("\n");
2850
+ embed.addFields({ name: "Findings", value: findingsText.slice(0, 1024) });
2851
+ } else {
2852
+ embed.addFields({ name: "Findings", value: "No significant findings." });
2853
+ }
2854
+ await interaction.editReply({ embeds: [embed] });
2855
+ }
2856
+ async function handleHelpCommand(interaction) {
2857
+ await interaction.reply({
2858
+ embeds: [
2859
+ new EmbedBuilder().setTitle("Vizzor Commands").setColor(5793266).setDescription(
2860
+ "`/scan <address>` \u2014 Analyze token/project risk\n`/trends` \u2014 Trending tokens + market data\n`/track <wallet>` \u2014 Wallet forensics\n`/ico` \u2014 Upcoming ICOs & fundraising rounds\n`/audit <contract>` \u2014 Smart contract audit\n`/help` \u2014 Show this message\n\n_Mention the bot for AI chat guidance._"
2861
+ ).setFooter({ text: "Vizzor by 7ayLabs" })
2862
+ ],
2863
+ ephemeral: true
2864
+ });
2865
+ }
2681
2866
  var init_commands = __esm({
2682
2867
  "src/discord/commands/index.ts"() {
2683
2868
  "use strict";
@@ -2685,6 +2870,12 @@ var init_commands = __esm({
2685
2870
  init_loader();
2686
2871
  init_project_analyzer();
2687
2872
  init_risk_scorer();
2873
+ init_wallet_analyzer();
2874
+ init_contract_auditor();
2875
+ init_market();
2876
+ init_ico_tracker();
2877
+ init_defillama();
2878
+ init_rate_limit();
2688
2879
  }
2689
2880
  });
2690
2881
 
@@ -2695,14 +2886,19 @@ __export(bot_exports, {
2695
2886
  });
2696
2887
  import { Client, GatewayIntentBits, REST, Routes } from "discord.js";
2697
2888
  async function startDiscordBot() {
2698
- const config2 = getConfig();
2889
+ const config2 = loadConfig();
2699
2890
  const token = config2.discordToken;
2700
2891
  if (!token) {
2701
2892
  throw new Error("Discord token not configured. Run: vizzor config set discordToken <token>");
2702
2893
  }
2703
2894
  const client2 = new Client({
2704
- intents: [GatewayIntentBits.Guilds]
2895
+ intents: [
2896
+ GatewayIntentBits.Guilds,
2897
+ GatewayIntentBits.GuildMessages,
2898
+ GatewayIntentBits.MessageContent
2899
+ ]
2705
2900
  });
2901
+ startRateLimitCleanup();
2706
2902
  client2.once("ready", async (readyClient) => {
2707
2903
  console.log(`Discord bot logged in as ${readyClient.user.tag}`);
2708
2904
  const rest = new REST({ version: "10" }).setToken(token);
@@ -2722,6 +2918,13 @@ async function startDiscordBot() {
2722
2918
  if (!interaction.isChatInputCommand()) return;
2723
2919
  await handleSlashCommand(interaction);
2724
2920
  });
2921
+ client2.on("messageCreate", async (message) => {
2922
+ if (message.author.bot) return;
2923
+ if (!client2.user || !message.mentions.has(client2.user)) return;
2924
+ await message.reply(
2925
+ "Use slash commands for on-chain intelligence:\n`/scan` `/trends` `/track` `/ico` `/audit` `/help`\n\nFor full AI-powered predictions, run `vizzor` in your terminal."
2926
+ );
2927
+ });
2725
2928
  await client2.login(token);
2726
2929
  }
2727
2930
  var init_bot = __esm({
@@ -2729,6 +2932,7 @@ var init_bot = __esm({
2729
2932
  "use strict";
2730
2933
  init_loader();
2731
2934
  init_commands();
2935
+ init_rate_limit();
2732
2936
  }
2733
2937
  });
2734
2938
 
@@ -3035,36 +3239,36 @@ async function rateLimitMiddleware(ctx, next) {
3035
3239
  return;
3036
3240
  }
3037
3241
  const now = Date.now();
3038
- const entry = userLimits.get(userId);
3242
+ const entry = userLimits2.get(userId);
3039
3243
  if (!entry || now >= entry.resetAt) {
3040
- userLimits.set(userId, { count: 1, resetAt: now + WINDOW_MS });
3244
+ userLimits2.set(userId, { count: 1, resetAt: now + WINDOW_MS2 });
3041
3245
  await next();
3042
3246
  return;
3043
3247
  }
3044
- if (entry.count >= MAX_REQUESTS) {
3248
+ if (entry.count >= MAX_REQUESTS2) {
3045
3249
  await ctx.reply("Rate limited. Please wait a moment before sending more commands.");
3046
3250
  return;
3047
3251
  }
3048
3252
  entry.count++;
3049
3253
  await next();
3050
3254
  }
3051
- function startRateLimitCleanup(intervalMs = 3e5) {
3255
+ function startRateLimitCleanup2(intervalMs = 3e5) {
3052
3256
  return setInterval(() => {
3053
3257
  const now = Date.now();
3054
- for (const [userId, entry] of userLimits) {
3258
+ for (const [userId, entry] of userLimits2) {
3055
3259
  if (now >= entry.resetAt) {
3056
- userLimits.delete(userId);
3260
+ userLimits2.delete(userId);
3057
3261
  }
3058
3262
  }
3059
3263
  }, intervalMs);
3060
3264
  }
3061
- var userLimits, MAX_REQUESTS, WINDOW_MS;
3062
- var init_rate_limit = __esm({
3265
+ var userLimits2, MAX_REQUESTS2, WINDOW_MS2;
3266
+ var init_rate_limit2 = __esm({
3063
3267
  "src/telegram/middleware/rate-limit.ts"() {
3064
3268
  "use strict";
3065
- userLimits = /* @__PURE__ */ new Map();
3066
- MAX_REQUESTS = 10;
3067
- WINDOW_MS = 6e4;
3269
+ userLimits2 = /* @__PURE__ */ new Map();
3270
+ MAX_REQUESTS2 = 10;
3271
+ WINDOW_MS2 = 6e4;
3068
3272
  }
3069
3273
  });
3070
3274
 
@@ -3120,7 +3324,7 @@ async function startTelegramBot() {
3120
3324
  bot.catch((err) => {
3121
3325
  logger.error({ err: err.message }, "Telegram bot error");
3122
3326
  });
3123
- const cleanupInterval = startRateLimitCleanup();
3327
+ const cleanupInterval = startRateLimitCleanup2();
3124
3328
  logger.info("Telegram bot starting...");
3125
3329
  await bot.start({
3126
3330
  onStart: (botInfo) => {
@@ -3135,7 +3339,7 @@ var init_bot2 = __esm({
3135
3339
  "use strict";
3136
3340
  init_loader();
3137
3341
  init_commands2();
3138
- init_rate_limit();
3342
+ init_rate_limit2();
3139
3343
  init_logger();
3140
3344
  logger = createLogger("telegram-bot");
3141
3345
  }
@@ -3144,7 +3348,8 @@ var init_bot2 = __esm({
3144
3348
  // src/cli/commands/bot.ts
3145
3349
  var bot_exports3 = {};
3146
3350
  __export(bot_exports3, {
3147
- handleBotStart: () => handleBotStart
3351
+ handleBotStart: () => handleBotStart,
3352
+ handleBotValidate: () => handleBotValidate
3148
3353
  });
3149
3354
  import chalk7 from "chalk";
3150
3355
  async function handleBotStart(options) {
@@ -3179,6 +3384,68 @@ async function handleBotStart(options) {
3179
3384
  process.on("SIGTERM", shutdown);
3180
3385
  await Promise.all(promises);
3181
3386
  }
3387
+ function handleBotValidate() {
3388
+ const config2 = getConfig();
3389
+ console.log(chalk7.bold("\nVizzor Bot Configuration Check\n"));
3390
+ const checks = [
3391
+ {
3392
+ label: "Anthropic API Key",
3393
+ value: config2.anthropicApiKey,
3394
+ required: true,
3395
+ key: "anthropicApiKey"
3396
+ },
3397
+ {
3398
+ label: "Etherscan API Key",
3399
+ value: config2.etherscanApiKey,
3400
+ required: true,
3401
+ key: "etherscanApiKey"
3402
+ },
3403
+ { label: "Discord Token", value: config2.discordToken, required: false, key: "discordToken" },
3404
+ {
3405
+ label: "Discord Guild ID",
3406
+ value: config2.discordGuildId,
3407
+ required: false,
3408
+ key: "discordGuildId"
3409
+ },
3410
+ { label: "Telegram Token", value: config2.telegramToken, required: false, key: "telegramToken" },
3411
+ {
3412
+ label: "CryptoPanic Key",
3413
+ value: config2.cryptopanicApiKey,
3414
+ required: false,
3415
+ key: "cryptopanicApiKey"
3416
+ }
3417
+ ];
3418
+ let allRequired = true;
3419
+ for (const check of checks) {
3420
+ const set = hasKey(check.value);
3421
+ const status = set ? chalk7.green("OK") : check.required ? chalk7.red("MISSING") : chalk7.yellow("NOT SET");
3422
+ const masked = set ? maskKey(check.value) : chalk7.dim("(not set)");
3423
+ console.log(` ${status.padEnd(18)} ${check.label.padEnd(20)} ${masked}`);
3424
+ if (check.required && !set) allRequired = false;
3425
+ }
3426
+ console.log();
3427
+ if (hasKey(config2.discordToken)) {
3428
+ console.log(chalk7.green(" Discord bot: Ready to start"));
3429
+ } else {
3430
+ console.log(chalk7.yellow(" Discord bot: Set discordToken to enable"));
3431
+ console.log(chalk7.dim(" vizzor config set discordToken <token>"));
3432
+ }
3433
+ if (hasKey(config2.telegramToken)) {
3434
+ console.log(chalk7.green(" Telegram bot: Ready to start"));
3435
+ } else {
3436
+ console.log(chalk7.yellow(" Telegram bot: Set telegramToken to enable"));
3437
+ console.log(chalk7.dim(" vizzor config set telegramToken <token>"));
3438
+ }
3439
+ console.log();
3440
+ if (!allRequired) {
3441
+ console.log(chalk7.red("Required keys are missing. Run: vizzor config set <key> <value>"));
3442
+ } else if (hasKey(config2.discordToken) && hasKey(config2.telegramToken)) {
3443
+ console.log(chalk7.green("All bots ready. Run: vizzor bot start --all"));
3444
+ } else if (hasKey(config2.discordToken) || hasKey(config2.telegramToken)) {
3445
+ const which = hasKey(config2.discordToken) ? "--discord" : "--telegram";
3446
+ console.log(chalk7.green(`Bot ready. Run: vizzor bot start ${which}`));
3447
+ }
3448
+ }
3182
3449
  var init_bot3 = __esm({
3183
3450
  "src/cli/commands/bot.ts"() {
3184
3451
  "use strict";
@@ -9024,6 +9291,10 @@ botCmd.command("start").description("Start bot(s)").option("--discord", "Start D
9024
9291
  const { handleBotStart: handleBotStart2 } = await Promise.resolve().then(() => (init_bot3(), bot_exports3));
9025
9292
  await handleBotStart2(options);
9026
9293
  });
9294
+ botCmd.command("validate").description("Check bot token configuration").action(async () => {
9295
+ const { handleBotValidate: handleBotValidate2 } = await Promise.resolve().then(() => (init_bot3(), bot_exports3));
9296
+ handleBotValidate2();
9297
+ });
9027
9298
  var args = process.argv.slice(2);
9028
9299
  if (args.length === 0) {
9029
9300
  await loadConfig();