@vizzor/cli 0.10.0 → 0.10.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/dist/index.js +286 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,13 +62,13 @@ var init_schema = __esm({
|
|
|
62
62
|
api: z.object({
|
|
63
63
|
port: z.number().default(3e3),
|
|
64
64
|
host: z.string().default("0.0.0.0"),
|
|
65
|
-
enableAuth: z.boolean().default(
|
|
66
|
-
corsOrigin: z.string().default("
|
|
65
|
+
enableAuth: z.boolean().default(true),
|
|
66
|
+
corsOrigin: z.string().default("http://localhost:3000")
|
|
67
67
|
}).default(() => ({
|
|
68
68
|
port: 3e3,
|
|
69
69
|
host: "0.0.0.0",
|
|
70
|
-
enableAuth:
|
|
71
|
-
corsOrigin: "
|
|
70
|
+
enableAuth: true,
|
|
71
|
+
corsOrigin: "http://localhost:3000"
|
|
72
72
|
})),
|
|
73
73
|
n8n: z.object({
|
|
74
74
|
enabled: z.boolean().default(false),
|
|
@@ -82,7 +82,7 @@ var init_schema = __esm({
|
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
// src/config/loader.ts
|
|
85
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
85
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
|
|
86
86
|
import { join } from "path";
|
|
87
87
|
import { homedir } from "os";
|
|
88
88
|
import yaml from "yaml";
|
|
@@ -209,7 +209,8 @@ function saveConfigValue(key, value) {
|
|
|
209
209
|
raw[key] = value;
|
|
210
210
|
}
|
|
211
211
|
vizzorConfigSchema.parse(raw);
|
|
212
|
-
writeFileSync(configPath, yaml.stringify(raw), "utf-8");
|
|
212
|
+
writeFileSync(configPath, yaml.stringify(raw), { encoding: "utf-8", mode: 384 });
|
|
213
|
+
chmodSync(configPath, 384);
|
|
213
214
|
cachedConfig = null;
|
|
214
215
|
loadConfig();
|
|
215
216
|
}
|
|
@@ -250,6 +251,20 @@ var init_loader = __esm({
|
|
|
250
251
|
}
|
|
251
252
|
});
|
|
252
253
|
|
|
254
|
+
// src/utils/validate.ts
|
|
255
|
+
function assertValidAddress(address) {
|
|
256
|
+
if (!EVM_ADDRESS_RE.test(address)) {
|
|
257
|
+
throw new Error(`Invalid address format: expected 0x followed by 40 hex characters`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
var EVM_ADDRESS_RE;
|
|
261
|
+
var init_validate = __esm({
|
|
262
|
+
"src/utils/validate.ts"() {
|
|
263
|
+
"use strict";
|
|
264
|
+
EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
253
268
|
// src/chains/evm/abi/erc20.ts
|
|
254
269
|
var erc20Abi;
|
|
255
270
|
var init_erc20 = __esm({
|
|
@@ -600,6 +615,7 @@ var CHAIN_MAP, EvmAdapter;
|
|
|
600
615
|
var init_adapter = __esm({
|
|
601
616
|
"src/chains/evm/adapter.ts"() {
|
|
602
617
|
"use strict";
|
|
618
|
+
init_validate();
|
|
603
619
|
init_erc20();
|
|
604
620
|
init_etherscan();
|
|
605
621
|
init_constants();
|
|
@@ -651,18 +667,23 @@ var init_adapter = __esm({
|
|
|
651
667
|
isConnected() {
|
|
652
668
|
return this.client !== null;
|
|
653
669
|
}
|
|
670
|
+
/** Validate and cast to viem Address. */
|
|
671
|
+
toAddress(address) {
|
|
672
|
+
assertValidAddress(address);
|
|
673
|
+
return this.toAddress(address);
|
|
674
|
+
}
|
|
654
675
|
// ── Balances ────────────────────────────────────────────────────────────
|
|
655
676
|
async getBalance(address) {
|
|
656
677
|
const client2 = this.requireClient();
|
|
657
|
-
return client2.getBalance({ address });
|
|
678
|
+
return client2.getBalance({ address: this.toAddress(address) });
|
|
658
679
|
}
|
|
659
680
|
async getTokenBalance(address, tokenAddress) {
|
|
660
681
|
const client2 = this.requireClient();
|
|
661
682
|
const balance = await client2.readContract({
|
|
662
|
-
address: tokenAddress,
|
|
683
|
+
address: this.toAddress(tokenAddress),
|
|
663
684
|
abi: erc20Abi,
|
|
664
685
|
functionName: "balanceOf",
|
|
665
|
-
args: [address]
|
|
686
|
+
args: [this.toAddress(address)]
|
|
666
687
|
});
|
|
667
688
|
return balance;
|
|
668
689
|
}
|
|
@@ -687,13 +708,13 @@ var init_adapter = __esm({
|
|
|
687
708
|
// ── Contracts ───────────────────────────────────────────────────────────
|
|
688
709
|
async getContractCode(address) {
|
|
689
710
|
const client2 = this.requireClient();
|
|
690
|
-
const code = await client2.getCode({ address });
|
|
711
|
+
const code = await client2.getCode({ address: this.toAddress(address) });
|
|
691
712
|
return code ?? "0x";
|
|
692
713
|
}
|
|
693
714
|
async readContract(address, abi, functionName, args2) {
|
|
694
715
|
const client2 = this.requireClient();
|
|
695
716
|
return client2.readContract({
|
|
696
|
-
address,
|
|
717
|
+
address: this.toAddress(address),
|
|
697
718
|
abi,
|
|
698
719
|
functionName,
|
|
699
720
|
args: args2
|
|
@@ -702,7 +723,7 @@ var init_adapter = __esm({
|
|
|
702
723
|
async getContractEvents(address, abi, eventName, options) {
|
|
703
724
|
const client2 = this.requireClient();
|
|
704
725
|
const logs = await client2.getContractEvents({
|
|
705
|
-
address,
|
|
726
|
+
address: this.toAddress(address),
|
|
706
727
|
abi,
|
|
707
728
|
eventName,
|
|
708
729
|
fromBlock: options?.fromBlock,
|
|
@@ -720,7 +741,7 @@ var init_adapter = __esm({
|
|
|
720
741
|
// ── Tokens ──────────────────────────────────────────────────────────────
|
|
721
742
|
async getTokenInfo(address) {
|
|
722
743
|
const client2 = this.requireClient();
|
|
723
|
-
const tokenAddr = address;
|
|
744
|
+
const tokenAddr = this.toAddress(address);
|
|
724
745
|
const [name, symbol, decimals, totalSupply] = await Promise.all([
|
|
725
746
|
client2.readContract({
|
|
726
747
|
address: tokenAddr,
|
|
@@ -1342,6 +1363,7 @@ __export(scan_exports, {
|
|
|
1342
1363
|
import chalk from "chalk";
|
|
1343
1364
|
import ora from "ora";
|
|
1344
1365
|
async function handleScan(project, options) {
|
|
1366
|
+
assertValidAddress(project);
|
|
1345
1367
|
const spinner = ora(`Scanning ${project} on ${options.chain}...`).start();
|
|
1346
1368
|
try {
|
|
1347
1369
|
const adapter = getAdapter(options.chain);
|
|
@@ -1396,6 +1418,7 @@ var init_scan = __esm({
|
|
|
1396
1418
|
init_loader();
|
|
1397
1419
|
init_project_analyzer();
|
|
1398
1420
|
init_risk_scorer();
|
|
1421
|
+
init_validate();
|
|
1399
1422
|
}
|
|
1400
1423
|
});
|
|
1401
1424
|
|
|
@@ -1718,7 +1741,7 @@ async function fetchJson2(url) {
|
|
|
1718
1741
|
}
|
|
1719
1742
|
async function fetchTickerPrice(symbol) {
|
|
1720
1743
|
const pair = resolvePair(symbol);
|
|
1721
|
-
const data = await fetchJson2(`${BASE_URL3}/ticker/24hr?symbol=${pair}`);
|
|
1744
|
+
const data = await fetchJson2(`${BASE_URL3}/ticker/24hr?symbol=${encodeURIComponent(pair)}`);
|
|
1722
1745
|
return {
|
|
1723
1746
|
symbol: symbol.toUpperCase(),
|
|
1724
1747
|
price: parseFloat(data.lastPrice),
|
|
@@ -1728,7 +1751,7 @@ async function fetchTickerPrice(symbol) {
|
|
|
1728
1751
|
async function fetchKlines(symbol, interval, limit = 100) {
|
|
1729
1752
|
const pair = resolvePair(symbol);
|
|
1730
1753
|
const data = await fetchJson2(
|
|
1731
|
-
`${BASE_URL3}/klines?symbol=${pair}&interval=${interval}&limit=${limit}`
|
|
1754
|
+
`${BASE_URL3}/klines?symbol=${encodeURIComponent(pair)}&interval=${interval}&limit=${limit}`
|
|
1732
1755
|
);
|
|
1733
1756
|
return data.map((k) => ({
|
|
1734
1757
|
openTime: Number(k[0]),
|
|
@@ -1744,7 +1767,7 @@ async function fetchKlines(symbol, interval, limit = 100) {
|
|
|
1744
1767
|
}
|
|
1745
1768
|
async function fetchFundingRate(symbol) {
|
|
1746
1769
|
const pair = resolvePair(symbol);
|
|
1747
|
-
const data = await fetchJson2(`${FUTURES_URL}/premiumIndex?symbol=${pair}`);
|
|
1770
|
+
const data = await fetchJson2(`${FUTURES_URL}/premiumIndex?symbol=${encodeURIComponent(pair)}`);
|
|
1748
1771
|
const item = data[0];
|
|
1749
1772
|
if (!item) throw new Error(`No funding data for ${pair}`);
|
|
1750
1773
|
return {
|
|
@@ -1758,9 +1781,11 @@ async function fetchOpenInterest(symbol) {
|
|
|
1758
1781
|
const pair = resolvePair(symbol);
|
|
1759
1782
|
const [oiData, tickerData] = await Promise.all([
|
|
1760
1783
|
fetchJson2(
|
|
1761
|
-
`${FUTURES_URL}/openInterest?symbol=${pair}`
|
|
1784
|
+
`${FUTURES_URL}/openInterest?symbol=${encodeURIComponent(pair)}`
|
|
1762
1785
|
),
|
|
1763
|
-
fetchJson2(
|
|
1786
|
+
fetchJson2(
|
|
1787
|
+
`${FUTURES_URL}/ticker/price?symbol=${encodeURIComponent(pair)}`
|
|
1788
|
+
)
|
|
1764
1789
|
]);
|
|
1765
1790
|
const oi = parseFloat(oiData.openInterest);
|
|
1766
1791
|
const price = parseFloat(tickerData.lastPrice);
|
|
@@ -1790,7 +1815,7 @@ async function fetchTopGainersLosers(limit = 10) {
|
|
|
1790
1815
|
async function isValidSymbol(symbol) {
|
|
1791
1816
|
try {
|
|
1792
1817
|
const pair = resolvePair(symbol);
|
|
1793
|
-
await fetchJson2(`${BASE_URL3}/ticker/price?symbol=${pair}`);
|
|
1818
|
+
await fetchJson2(`${BASE_URL3}/ticker/price?symbol=${encodeURIComponent(pair)}`);
|
|
1794
1819
|
return true;
|
|
1795
1820
|
} catch {
|
|
1796
1821
|
return false;
|
|
@@ -2811,6 +2836,7 @@ __export(track_exports, {
|
|
|
2811
2836
|
import chalk3 from "chalk";
|
|
2812
2837
|
import ora3 from "ora";
|
|
2813
2838
|
async function handleTrack(wallet, options) {
|
|
2839
|
+
assertValidAddress(wallet);
|
|
2814
2840
|
const spinner = ora3(`Analyzing wallet ${wallet.slice(0, 10)}... on ${options.chain}`).start();
|
|
2815
2841
|
try {
|
|
2816
2842
|
const adapter = getAdapter(options.chain);
|
|
@@ -2871,6 +2897,7 @@ var init_track = __esm({
|
|
|
2871
2897
|
init_registry();
|
|
2872
2898
|
init_loader();
|
|
2873
2899
|
init_wallet_analyzer();
|
|
2900
|
+
init_validate();
|
|
2874
2901
|
}
|
|
2875
2902
|
});
|
|
2876
2903
|
|
|
@@ -3163,6 +3190,7 @@ __export(audit_exports, {
|
|
|
3163
3190
|
import chalk5 from "chalk";
|
|
3164
3191
|
import ora5 from "ora";
|
|
3165
3192
|
async function handleAudit(contract, options) {
|
|
3193
|
+
assertValidAddress(contract);
|
|
3166
3194
|
const spinner = ora5(`Auditing contract ${contract.slice(0, 10)}... on ${options.chain}`).start();
|
|
3167
3195
|
try {
|
|
3168
3196
|
const adapter = getAdapter(options.chain);
|
|
@@ -3240,6 +3268,7 @@ var init_audit = __esm({
|
|
|
3240
3268
|
init_registry();
|
|
3241
3269
|
init_loader();
|
|
3242
3270
|
init_contract_auditor();
|
|
3271
|
+
init_validate();
|
|
3243
3272
|
}
|
|
3244
3273
|
});
|
|
3245
3274
|
|
|
@@ -4292,7 +4321,9 @@ async function fetchJson6(url) {
|
|
|
4292
4321
|
}
|
|
4293
4322
|
async function checkTokenSecurity(contractAddress, chain) {
|
|
4294
4323
|
const chainId = resolveChainId(chain);
|
|
4295
|
-
const data = await fetchJson6(
|
|
4324
|
+
const data = await fetchJson6(
|
|
4325
|
+
`${BASE_URL6}/token_security/${encodeURIComponent(chainId)}?contract_addresses=${encodeURIComponent(contractAddress.toLowerCase())}`
|
|
4326
|
+
);
|
|
4296
4327
|
if (data.code !== 1) return null;
|
|
4297
4328
|
const addr = contractAddress.toLowerCase();
|
|
4298
4329
|
const raw = data.result[addr];
|
|
@@ -4347,7 +4378,9 @@ async function checkTokenSecurity(contractAddress, chain) {
|
|
|
4347
4378
|
}
|
|
4348
4379
|
async function checkAddressSecurity(address, chain) {
|
|
4349
4380
|
const chainId = resolveChainId(chain);
|
|
4350
|
-
const data = await fetchJson6(
|
|
4381
|
+
const data = await fetchJson6(
|
|
4382
|
+
`${BASE_URL6}/address_security/${encodeURIComponent(chainId)}?address=${encodeURIComponent(address.toLowerCase())}`
|
|
4383
|
+
);
|
|
4351
4384
|
if (data.code !== 1) return null;
|
|
4352
4385
|
const raw = data.result;
|
|
4353
4386
|
const toBool = (v) => v === "1" || v === 1 || v === true;
|
|
@@ -4379,6 +4412,77 @@ var init_goplus = __esm({
|
|
|
4379
4412
|
}
|
|
4380
4413
|
});
|
|
4381
4414
|
|
|
4415
|
+
// src/ai/sanitize.ts
|
|
4416
|
+
function sanitizeExternalData(text, maxLen = LIMITS.generic) {
|
|
4417
|
+
let cleaned = text;
|
|
4418
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
4419
|
+
cleaned = cleaned.replace(pattern, "[FILTERED]");
|
|
4420
|
+
}
|
|
4421
|
+
cleaned = cleaned.replace(/^#{1,6}\s+/gm, "");
|
|
4422
|
+
cleaned = cleaned.replace(/```[\s\S]*?```/g, "[CODE]");
|
|
4423
|
+
if (cleaned.length > maxLen) {
|
|
4424
|
+
cleaned = cleaned.slice(0, maxLen) + "...";
|
|
4425
|
+
}
|
|
4426
|
+
return cleaned;
|
|
4427
|
+
}
|
|
4428
|
+
function sanitizeTokenName(name) {
|
|
4429
|
+
return sanitizeExternalData(name, LIMITS.tokenName);
|
|
4430
|
+
}
|
|
4431
|
+
function sanitizeHeadline(headline) {
|
|
4432
|
+
return sanitizeExternalData(headline, LIMITS.headline);
|
|
4433
|
+
}
|
|
4434
|
+
function wrapUntrustedData(label, data) {
|
|
4435
|
+
return `[BEGIN EXTERNAL DATA: ${label}]
|
|
4436
|
+
${data}
|
|
4437
|
+
[END EXTERNAL DATA: ${label}]`;
|
|
4438
|
+
}
|
|
4439
|
+
function sanitizeToolResult(result) {
|
|
4440
|
+
if (typeof result === "string") {
|
|
4441
|
+
return sanitizeExternalData(result);
|
|
4442
|
+
}
|
|
4443
|
+
if (Array.isArray(result)) {
|
|
4444
|
+
return result.map(sanitizeToolResult);
|
|
4445
|
+
}
|
|
4446
|
+
if (result !== null && typeof result === "object") {
|
|
4447
|
+
const sanitized = {};
|
|
4448
|
+
for (const [key, value] of Object.entries(result)) {
|
|
4449
|
+
sanitized[key] = sanitizeToolResult(value);
|
|
4450
|
+
}
|
|
4451
|
+
return sanitized;
|
|
4452
|
+
}
|
|
4453
|
+
return result;
|
|
4454
|
+
}
|
|
4455
|
+
var INJECTION_PATTERNS, LIMITS;
|
|
4456
|
+
var init_sanitize = __esm({
|
|
4457
|
+
"src/ai/sanitize.ts"() {
|
|
4458
|
+
"use strict";
|
|
4459
|
+
INJECTION_PATTERNS = [
|
|
4460
|
+
/ignore\s+(all\s+)?previous\s+instructions/gi,
|
|
4461
|
+
/ignore\s+(all\s+)?above/gi,
|
|
4462
|
+
/you\s+are\s+now/gi,
|
|
4463
|
+
/system\s*:/gi,
|
|
4464
|
+
/assistant\s*:/gi,
|
|
4465
|
+
/\[system\]/gi,
|
|
4466
|
+
/\[instruction\]/gi,
|
|
4467
|
+
/<\/?system>/gi,
|
|
4468
|
+
/repeat\s+(everything|the\s+prompt|your\s+instructions)/gi,
|
|
4469
|
+
/reveal\s+(your|the)\s+(prompt|instructions|config)/gi,
|
|
4470
|
+
/forget\s+(everything|your\s+instructions)/gi,
|
|
4471
|
+
/override\s+(previous|your|all)/gi,
|
|
4472
|
+
/new\s+instructions?\s*:/gi,
|
|
4473
|
+
/\bdo\s+not\s+follow\b/gi,
|
|
4474
|
+
/\bact\s+as\b/gi,
|
|
4475
|
+
/\brole\s*:\s*(system|assistant)\b/gi
|
|
4476
|
+
];
|
|
4477
|
+
LIMITS = {
|
|
4478
|
+
tokenName: 60,
|
|
4479
|
+
headline: 250,
|
|
4480
|
+
description: 500,
|
|
4481
|
+
generic: 1e3
|
|
4482
|
+
};
|
|
4483
|
+
}
|
|
4484
|
+
});
|
|
4485
|
+
|
|
4382
4486
|
// src/ai/context-injector.ts
|
|
4383
4487
|
async function buildContextBlock(userMessage) {
|
|
4384
4488
|
const lower = userMessage.toLowerCase();
|
|
@@ -4601,7 +4705,8 @@ async function buildContextBlock(userMessage) {
|
|
|
4601
4705
|
}
|
|
4602
4706
|
output.push(...buildInstructions(queryType, hasSpecificToken, isComplexQuery));
|
|
4603
4707
|
output.push("");
|
|
4604
|
-
|
|
4708
|
+
const raw = output.join("\n");
|
|
4709
|
+
return wrapUntrustedData("MARKET_CONTEXT", raw);
|
|
4605
4710
|
}
|
|
4606
4711
|
function buildInstructions(queryType, hasSpecificToken, isComplexQuery) {
|
|
4607
4712
|
const base2 = [
|
|
@@ -4685,7 +4790,7 @@ async function fetchTokenData(tokens, address) {
|
|
|
4685
4790
|
const pair = pairs[0];
|
|
4686
4791
|
if (pair) {
|
|
4687
4792
|
lines.push(
|
|
4688
|
-
`${pair.baseToken.name} (${pair.baseToken.symbol}) on ${pair.chainId}:`,
|
|
4793
|
+
`${sanitizeTokenName(pair.baseToken.name)} (${sanitizeTokenName(pair.baseToken.symbol)}) on ${pair.chainId}:`,
|
|
4689
4794
|
` Price: $${pair.priceUsd ?? "?"}`,
|
|
4690
4795
|
` 24h Volume: $${(pair.volume?.h24 ?? 0).toLocaleString()}`,
|
|
4691
4796
|
` Liquidity: $${(pair.liquidity?.usd ?? 0).toLocaleString()}`,
|
|
@@ -4754,7 +4859,7 @@ async function fetchTokenData(tokens, address) {
|
|
|
4754
4859
|
const pair = pairs[0];
|
|
4755
4860
|
if (pair) {
|
|
4756
4861
|
lines.push(
|
|
4757
|
-
`${pair.baseToken.name} (${pair.baseToken.symbol}) on ${pair.chainId}:`,
|
|
4862
|
+
`${sanitizeTokenName(pair.baseToken.name)} (${sanitizeTokenName(pair.baseToken.symbol)}) on ${pair.chainId}:`,
|
|
4758
4863
|
` Price: $${pair.priceUsd ?? "?"}`,
|
|
4759
4864
|
` 24h Volume: $${(pair.volume?.h24 ?? 0).toLocaleString()}`,
|
|
4760
4865
|
` 24h Change: ${(pair.priceChange?.h24 ?? 0) > 0 ? "+" : ""}${(pair.priceChange?.h24 ?? 0).toFixed(2)}%`,
|
|
@@ -4773,7 +4878,7 @@ async function fetchTrendingData() {
|
|
|
4773
4878
|
const lines = ["## Trending Tokens (live)"];
|
|
4774
4879
|
for (const t of trending.slice(0, 10)) {
|
|
4775
4880
|
lines.push(
|
|
4776
|
-
`- ${t.name} (${t.symbol}) on ${t.chain}: $${t.priceUsd} | 24h: ${t.priceChange24h > 0 ? "+" : ""}${t.priceChange24h.toFixed(1)}% | Vol: $${t.volume24h.toLocaleString()} [${t.source}]`
|
|
4881
|
+
`- ${sanitizeTokenName(t.name)} (${sanitizeTokenName(t.symbol)}) on ${t.chain}: $${t.priceUsd} | 24h: ${t.priceChange24h > 0 ? "+" : ""}${t.priceChange24h.toFixed(1)}% | Vol: $${t.volume24h.toLocaleString()} [${t.source}]`
|
|
4777
4882
|
);
|
|
4778
4883
|
}
|
|
4779
4884
|
return lines.join("\n");
|
|
@@ -4797,7 +4902,9 @@ async function fetchNewsData(symbol) {
|
|
|
4797
4902
|
const n = headlines[i];
|
|
4798
4903
|
const ml = mlResults[i];
|
|
4799
4904
|
const label = ml ? `${ml.sentiment.toUpperCase()} (${(ml.confidence * 100).toFixed(0)}%)` : n.sentiment.toUpperCase();
|
|
4800
|
-
lines.push(
|
|
4905
|
+
lines.push(
|
|
4906
|
+
`- [${label}] ${sanitizeHeadline(n.title)} (${sanitizeTokenName(n.source.title)}, ${n.publishedAt})`
|
|
4907
|
+
);
|
|
4801
4908
|
}
|
|
4802
4909
|
const avgScore = mlResults.reduce((s, r) => s + r.score, 0) / mlResults.length;
|
|
4803
4910
|
const avgSentiment = avgScore > 0.2 ? "BULLISH" : avgScore < -0.2 ? "BEARISH" : "NEUTRAL";
|
|
@@ -4810,7 +4917,7 @@ ML Aggregate Sentiment: ${avgSentiment} (score: ${avgScore.toFixed(3)})`);
|
|
|
4810
4917
|
}
|
|
4811
4918
|
for (const n of headlines) {
|
|
4812
4919
|
lines.push(
|
|
4813
|
-
`- [${n.sentiment.toUpperCase()}] ${n.title} (${n.source.title}, ${n.publishedAt})`
|
|
4920
|
+
`- [${n.sentiment.toUpperCase()}] ${sanitizeHeadline(n.title)} (${sanitizeTokenName(n.source.title)}, ${n.publishedAt})`
|
|
4814
4921
|
);
|
|
4815
4922
|
}
|
|
4816
4923
|
return lines.join("\n");
|
|
@@ -4850,7 +4957,7 @@ async function fetchRaisesData() {
|
|
|
4850
4957
|
const amount = r.amount ? r.amount >= 1e9 ? `$${(r.amount / 1e9).toFixed(1)}B` : r.amount >= 1e6 ? `$${(r.amount / 1e6).toFixed(1)}M` : r.amount >= 1e3 ? `$${(r.amount / 1e3).toFixed(0)}K` : `$${r.amount.toLocaleString()}` : "undisclosed";
|
|
4851
4958
|
const date = new Date(r.date * 1e3).toISOString().split("T")[0];
|
|
4852
4959
|
lines.push(
|
|
4853
|
-
`- ${r.name} \u2014 ${r.round} (${amount}) on ${r.chains.join(", ") || "multi-chain"} [${date}]${r.leadInvestors.length > 0 ? ` Led by: ${r.leadInvestors.join(", ")}` : ""}`
|
|
4960
|
+
`- ${sanitizeTokenName(r.name)} \u2014 ${sanitizeExternalData(r.round, 50)} (${amount}) on ${r.chains.join(", ") || "multi-chain"} [${date}]${r.leadInvestors.length > 0 ? ` Led by: ${r.leadInvestors.map((inv) => sanitizeTokenName(inv)).join(", ")}` : ""}`
|
|
4854
4961
|
);
|
|
4855
4962
|
}
|
|
4856
4963
|
return lines.join("\n");
|
|
@@ -4865,7 +4972,9 @@ async function fetchPumpData() {
|
|
|
4865
4972
|
const lines = ["## Latest Pump.fun Launches (Solana)"];
|
|
4866
4973
|
for (const c of coins) {
|
|
4867
4974
|
const mcap = c.usd_market_cap ? `$${c.usd_market_cap.toFixed(0)}` : "?";
|
|
4868
|
-
lines.push(
|
|
4975
|
+
lines.push(
|
|
4976
|
+
`- ${sanitizeTokenName(c.name)} (${sanitizeTokenName(c.symbol)}) \u2014 MC: ${mcap} | Replies: ${c.reply_count}`
|
|
4977
|
+
);
|
|
4869
4978
|
}
|
|
4870
4979
|
return lines.join("\n");
|
|
4871
4980
|
} catch {
|
|
@@ -5785,6 +5894,7 @@ var init_context_injector = __esm({
|
|
|
5785
5894
|
init_loader();
|
|
5786
5895
|
init_constants();
|
|
5787
5896
|
init_client();
|
|
5897
|
+
init_sanitize();
|
|
5788
5898
|
PRICE_KEYWORDS = ["price", "worth", "cost", "value", "how much"];
|
|
5789
5899
|
TRENDING_KEYWORDS = ["trending", "hot", "popular", "top", "best", "hype"];
|
|
5790
5900
|
NEWS_KEYWORDS = ["news", "latest", "update", "happening", "announcement"];
|
|
@@ -6524,7 +6634,12 @@ function getCached(key) {
|
|
|
6524
6634
|
getDb().prepare("DELETE FROM cache WHERE key = ?").run(key);
|
|
6525
6635
|
return null;
|
|
6526
6636
|
}
|
|
6527
|
-
|
|
6637
|
+
try {
|
|
6638
|
+
return JSON.parse(row.value);
|
|
6639
|
+
} catch {
|
|
6640
|
+
getDb().prepare("DELETE FROM cache WHERE key = ?").run(key);
|
|
6641
|
+
return null;
|
|
6642
|
+
}
|
|
6528
6643
|
}
|
|
6529
6644
|
function setCache(key, value, ttlSeconds) {
|
|
6530
6645
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -6556,12 +6671,14 @@ var init_engine = __esm({
|
|
|
6556
6671
|
logger = createLogger("agent-engine");
|
|
6557
6672
|
AgentEngine = class {
|
|
6558
6673
|
timer = null;
|
|
6674
|
+
running = false;
|
|
6559
6675
|
state;
|
|
6560
6676
|
strategy;
|
|
6561
6677
|
constructor(config2) {
|
|
6562
6678
|
this.strategy = getStrategy(config2.strategy);
|
|
6679
|
+
const interval = Math.max(30, config2.interval);
|
|
6563
6680
|
this.state = {
|
|
6564
|
-
config: config2,
|
|
6681
|
+
config: { ...config2, interval },
|
|
6565
6682
|
status: "idle",
|
|
6566
6683
|
lastCycle: null,
|
|
6567
6684
|
cycleCount: 0,
|
|
@@ -6575,20 +6692,30 @@ var init_engine = __esm({
|
|
|
6575
6692
|
if (this.state.status === "running") return;
|
|
6576
6693
|
this.state.status = "running";
|
|
6577
6694
|
this.state.error = null;
|
|
6695
|
+
this.running = true;
|
|
6578
6696
|
logger.info(`Agent ${this.state.config.name} started (${this.strategy.name})`);
|
|
6579
|
-
void this.
|
|
6580
|
-
this.timer = setInterval(() => {
|
|
6581
|
-
void this.runCycle();
|
|
6582
|
-
}, this.state.config.interval * 1e3);
|
|
6697
|
+
void this.scheduleNextCycle(true);
|
|
6583
6698
|
}
|
|
6584
6699
|
stop() {
|
|
6700
|
+
this.running = false;
|
|
6585
6701
|
if (this.timer) {
|
|
6586
|
-
|
|
6702
|
+
clearTimeout(this.timer);
|
|
6587
6703
|
this.timer = null;
|
|
6588
6704
|
}
|
|
6589
6705
|
this.state.status = "stopped";
|
|
6590
6706
|
logger.info(`Agent ${this.state.config.name} stopped`);
|
|
6591
6707
|
}
|
|
6708
|
+
async scheduleNextCycle(immediate = false) {
|
|
6709
|
+
if (!this.running) return;
|
|
6710
|
+
if (!immediate) {
|
|
6711
|
+
await new Promise((resolve4) => {
|
|
6712
|
+
this.timer = setTimeout(resolve4, this.state.config.interval * 1e3);
|
|
6713
|
+
});
|
|
6714
|
+
}
|
|
6715
|
+
if (!this.running) return;
|
|
6716
|
+
await this.runCycle();
|
|
6717
|
+
void this.scheduleNextCycle(false);
|
|
6718
|
+
}
|
|
6592
6719
|
async runCycle() {
|
|
6593
6720
|
for (const symbol of this.state.config.pairs) {
|
|
6594
6721
|
try {
|
|
@@ -7011,11 +7138,15 @@ function deleteAgent(id) {
|
|
|
7011
7138
|
function startAgent(id) {
|
|
7012
7139
|
const config2 = getAgentById(id);
|
|
7013
7140
|
if (!config2) throw new Error(`Agent not found: ${id}`);
|
|
7014
|
-
|
|
7015
|
-
if (
|
|
7016
|
-
|
|
7017
|
-
|
|
7141
|
+
const existing = engines.get(id);
|
|
7142
|
+
if (existing) {
|
|
7143
|
+
const state = existing.getState();
|
|
7144
|
+
if (state.status === "running") {
|
|
7145
|
+
return state;
|
|
7146
|
+
}
|
|
7018
7147
|
}
|
|
7148
|
+
const engine = new AgentEngine(config2);
|
|
7149
|
+
engines.set(id, engine);
|
|
7019
7150
|
engine.start();
|
|
7020
7151
|
return engine.getState();
|
|
7021
7152
|
}
|
|
@@ -7052,23 +7183,43 @@ function logDecision(result) {
|
|
|
7052
7183
|
JSON.stringify(result.signals),
|
|
7053
7184
|
result.timestamp
|
|
7054
7185
|
);
|
|
7186
|
+
pruneCounter++;
|
|
7187
|
+
if (pruneCounter >= 100) {
|
|
7188
|
+
pruneCounter = 0;
|
|
7189
|
+
pruneDecisions(result.agentId, 1e3);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
function pruneDecisions(agentId, keep = 1e3) {
|
|
7193
|
+
ensureAgentTables();
|
|
7194
|
+
const result = getDb().prepare(
|
|
7195
|
+
`DELETE FROM agent_decisions WHERE agent_id = ? AND id NOT IN (
|
|
7196
|
+
SELECT id FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?
|
|
7197
|
+
)`
|
|
7198
|
+
).run(agentId, agentId, keep);
|
|
7199
|
+
return result.changes;
|
|
7055
7200
|
}
|
|
7056
7201
|
function getRecentDecisions(agentId, limit = 20) {
|
|
7057
7202
|
ensureAgentTables();
|
|
7058
7203
|
const rows = getDb().prepare(`SELECT * FROM agent_decisions WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?`).all(agentId, limit);
|
|
7059
|
-
return rows.map((r) =>
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7204
|
+
return rows.map((r) => {
|
|
7205
|
+
try {
|
|
7206
|
+
return {
|
|
7207
|
+
agentId: r.agent_id,
|
|
7208
|
+
symbol: r.symbol,
|
|
7209
|
+
timestamp: r.created_at,
|
|
7210
|
+
signals: JSON.parse(r.signals),
|
|
7211
|
+
decision: {
|
|
7212
|
+
action: r.action,
|
|
7213
|
+
confidence: r.confidence,
|
|
7214
|
+
reasoning: JSON.parse(r.reasoning)
|
|
7215
|
+
}
|
|
7216
|
+
};
|
|
7217
|
+
} catch {
|
|
7218
|
+
return null;
|
|
7068
7219
|
}
|
|
7069
|
-
}));
|
|
7220
|
+
}).filter((r) => r !== null);
|
|
7070
7221
|
}
|
|
7071
|
-
var STRATEGIES, engines;
|
|
7222
|
+
var STRATEGIES, engines, pruneCounter;
|
|
7072
7223
|
var init_manager = __esm({
|
|
7073
7224
|
"src/core/agent/manager.ts"() {
|
|
7074
7225
|
"use strict";
|
|
@@ -7083,6 +7234,7 @@ var init_manager = __esm({
|
|
|
7083
7234
|
"ml-adaptive": mlAdaptiveStrategy
|
|
7084
7235
|
};
|
|
7085
7236
|
engines = /* @__PURE__ */ new Map();
|
|
7237
|
+
pruneCounter = 0;
|
|
7086
7238
|
}
|
|
7087
7239
|
});
|
|
7088
7240
|
|
|
@@ -7549,6 +7701,10 @@ var init_store_factory = __esm({
|
|
|
7549
7701
|
|
|
7550
7702
|
// src/ai/tool-handler.ts
|
|
7551
7703
|
async function handleTool(name, input) {
|
|
7704
|
+
const raw = await handleToolUnsafe(name, input);
|
|
7705
|
+
return sanitizeToolResult(raw);
|
|
7706
|
+
}
|
|
7707
|
+
async function handleToolUnsafe(name, input) {
|
|
7552
7708
|
const params = input;
|
|
7553
7709
|
switch (name) {
|
|
7554
7710
|
case "get_token_info": {
|
|
@@ -8248,6 +8404,7 @@ var init_tool_handler = __esm({
|
|
|
8248
8404
|
init_client();
|
|
8249
8405
|
init_feature_engineer();
|
|
8250
8406
|
init_store_factory();
|
|
8407
|
+
init_sanitize();
|
|
8251
8408
|
}
|
|
8252
8409
|
});
|
|
8253
8410
|
|
|
@@ -9288,13 +9445,16 @@ async function startDiscordBot() {
|
|
|
9288
9445
|
client2.on("messageCreate", async (message) => {
|
|
9289
9446
|
if (message.author.bot) return;
|
|
9290
9447
|
if (!client2.user || !message.mentions.has(client2.user)) return;
|
|
9291
|
-
|
|
9448
|
+
let text = message.content.replace(/<@!?\d+>/g, "").trim();
|
|
9292
9449
|
if (!text) {
|
|
9293
9450
|
await message.reply(
|
|
9294
9451
|
"Mention me with a question! e.g. `@Vizzor what is BTC price?`\nOr use slash commands: `/scan` `/trends` `/track` `/ico` `/audit` `/help`"
|
|
9295
9452
|
);
|
|
9296
9453
|
return;
|
|
9297
9454
|
}
|
|
9455
|
+
if (text.length > 2e3) {
|
|
9456
|
+
text = text.slice(0, 2e3);
|
|
9457
|
+
}
|
|
9298
9458
|
await message.reply("\u{1F52E} Analyzing...");
|
|
9299
9459
|
try {
|
|
9300
9460
|
const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
|
|
@@ -9964,8 +10124,11 @@ async function startTelegramBot() {
|
|
|
9964
10124
|
bot.use(rateLimitMiddleware);
|
|
9965
10125
|
registerCommands(bot);
|
|
9966
10126
|
bot.on("message:text", async (ctx) => {
|
|
9967
|
-
|
|
10127
|
+
let text = ctx.message.text;
|
|
9968
10128
|
if (text.startsWith("/")) return;
|
|
10129
|
+
if (text.length > 4e3) {
|
|
10130
|
+
text = text.slice(0, 4e3);
|
|
10131
|
+
}
|
|
9969
10132
|
await ctx.reply("\u{1F52E} Analyzing...");
|
|
9970
10133
|
try {
|
|
9971
10134
|
const response = await analyze(buildChatSystemPrompt(), text, VIZZOR_TOOLS);
|
|
@@ -10543,19 +10706,26 @@ var init_keys2 = __esm({
|
|
|
10543
10706
|
});
|
|
10544
10707
|
|
|
10545
10708
|
// src/api/auth/middleware.ts
|
|
10709
|
+
import { timingSafeEqual } from "crypto";
|
|
10546
10710
|
async function authMiddleware(request, reply) {
|
|
10547
|
-
if (PUBLIC_PATHS.some((p) => request.url === p)
|
|
10711
|
+
if (PUBLIC_PATHS.some((p) => request.url === p)) {
|
|
10712
|
+
return;
|
|
10713
|
+
}
|
|
10714
|
+
if (request.url === "/docs" || request.url.startsWith("/docs/")) {
|
|
10715
|
+
if (process.env["NODE_ENV"] === "production") {
|
|
10716
|
+
return reply.status(404).send({ error: "Not found" });
|
|
10717
|
+
}
|
|
10548
10718
|
return;
|
|
10549
10719
|
}
|
|
10550
10720
|
const apiKey = request.headers["x-api-key"];
|
|
10551
|
-
if (!apiKey) {
|
|
10721
|
+
if (!apiKey || apiKey.length > 256) {
|
|
10552
10722
|
return reply.status(401).send({
|
|
10553
10723
|
error: "Unauthorized",
|
|
10554
|
-
message: "Missing X-API-Key header"
|
|
10724
|
+
message: "Missing or invalid X-API-Key header"
|
|
10555
10725
|
});
|
|
10556
10726
|
}
|
|
10557
10727
|
const keyHash = hashApiKey(apiKey);
|
|
10558
|
-
const valid =
|
|
10728
|
+
const valid = validateKey2(keyHash);
|
|
10559
10729
|
if (!valid) {
|
|
10560
10730
|
return reply.status(403).send({
|
|
10561
10731
|
error: "Forbidden",
|
|
@@ -10563,20 +10733,43 @@ async function authMiddleware(request, reply) {
|
|
|
10563
10733
|
});
|
|
10564
10734
|
}
|
|
10565
10735
|
}
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10736
|
+
function validateKey2(keyHash) {
|
|
10737
|
+
try {
|
|
10738
|
+
const db2 = getDb();
|
|
10739
|
+
db2.exec(`
|
|
10740
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
10741
|
+
id TEXT PRIMARY KEY,
|
|
10742
|
+
label TEXT NOT NULL,
|
|
10743
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
10744
|
+
key_prefix TEXT NOT NULL,
|
|
10745
|
+
rate_limit INTEGER NOT NULL DEFAULT 100,
|
|
10746
|
+
created_at INTEGER NOT NULL,
|
|
10747
|
+
revoked_at INTEGER
|
|
10748
|
+
)
|
|
10749
|
+
`);
|
|
10750
|
+
const row = db2.prepare("SELECT key_hash FROM api_keys WHERE revoked_at IS NULL").all();
|
|
10751
|
+
if (row.length === 0) {
|
|
10752
|
+
return false;
|
|
10753
|
+
}
|
|
10754
|
+
const inputBuf = Buffer.from(keyHash, "hex");
|
|
10755
|
+
for (const r of row) {
|
|
10756
|
+
const storedBuf = Buffer.from(r.key_hash, "hex");
|
|
10757
|
+
if (inputBuf.length === storedBuf.length && timingSafeEqual(inputBuf, storedBuf)) {
|
|
10758
|
+
return true;
|
|
10759
|
+
}
|
|
10760
|
+
}
|
|
10761
|
+
return false;
|
|
10762
|
+
} catch {
|
|
10763
|
+
return false;
|
|
10764
|
+
}
|
|
10572
10765
|
}
|
|
10573
10766
|
var PUBLIC_PATHS;
|
|
10574
10767
|
var init_middleware = __esm({
|
|
10575
10768
|
"src/api/auth/middleware.ts"() {
|
|
10576
10769
|
"use strict";
|
|
10577
10770
|
init_keys2();
|
|
10578
|
-
|
|
10579
|
-
PUBLIC_PATHS = ["/health"
|
|
10771
|
+
init_cache();
|
|
10772
|
+
PUBLIC_PATHS = ["/health"];
|
|
10580
10773
|
}
|
|
10581
10774
|
});
|
|
10582
10775
|
|
|
@@ -10613,7 +10806,11 @@ import swagger from "@fastify/swagger";
|
|
|
10613
10806
|
import swaggerUi from "@fastify/swagger-ui";
|
|
10614
10807
|
async function startApiServer(options) {
|
|
10615
10808
|
const server = Fastify({ logger: false });
|
|
10616
|
-
|
|
10809
|
+
const isProd = process.env["NODE_ENV"] === "production";
|
|
10810
|
+
const origin = options.corsOrigin ?? "http://localhost:3000";
|
|
10811
|
+
await server.register(cors, {
|
|
10812
|
+
origin: isProd ? origin : true
|
|
10813
|
+
});
|
|
10617
10814
|
await server.register(rateLimit, {
|
|
10618
10815
|
max: 100,
|
|
10619
10816
|
timeWindow: "1 minute"
|
|
@@ -10623,7 +10820,7 @@ async function startApiServer(options) {
|
|
|
10623
10820
|
info: {
|
|
10624
10821
|
title: "Vizzor API",
|
|
10625
10822
|
description: "AI-powered crypto intelligence REST API",
|
|
10626
|
-
version: "0.
|
|
10823
|
+
version: "0.10.5"
|
|
10627
10824
|
},
|
|
10628
10825
|
servers: [{ url: `http://${options.host}:${options.port}` }],
|
|
10629
10826
|
components: {
|
|
@@ -10637,25 +10834,36 @@ async function startApiServer(options) {
|
|
|
10637
10834
|
}
|
|
10638
10835
|
}
|
|
10639
10836
|
});
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10837
|
+
if (!isProd) {
|
|
10838
|
+
await server.register(swaggerUi, {
|
|
10839
|
+
routePrefix: "/docs"
|
|
10840
|
+
});
|
|
10841
|
+
}
|
|
10842
|
+
if (options.enableAuth !== false) {
|
|
10644
10843
|
server.addHook("onRequest", authMiddleware);
|
|
10844
|
+
} else if (isProd) {
|
|
10845
|
+
log3.warn("API authentication is DISABLED in production \u2014 this is insecure");
|
|
10645
10846
|
}
|
|
10646
10847
|
server.setErrorHandler(errorHandler);
|
|
10647
|
-
server.get("/health", async () =>
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10848
|
+
server.get("/health", async () => {
|
|
10849
|
+
if (isProd) {
|
|
10850
|
+
return { status: "ok" };
|
|
10851
|
+
}
|
|
10852
|
+
return {
|
|
10853
|
+
status: "ok",
|
|
10854
|
+
version: "0.10.5",
|
|
10855
|
+
uptime: process.uptime(),
|
|
10856
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10857
|
+
};
|
|
10858
|
+
});
|
|
10653
10859
|
await server.register(registerMarketRoutes, { prefix: "/v1/market" });
|
|
10654
10860
|
await server.register(registerAnalysisRoutes, { prefix: "/v1/analysis" });
|
|
10655
10861
|
await server.register(registerSecurityRoutes, { prefix: "/v1/security" });
|
|
10656
10862
|
await server.listen({ port: options.port, host: options.host });
|
|
10657
10863
|
log3.info(`Vizzor API listening on ${options.host}:${options.port}`);
|
|
10658
|
-
|
|
10864
|
+
if (!isProd) {
|
|
10865
|
+
log3.info(`OpenAPI docs at http://${options.host}:${options.port}/docs`);
|
|
10866
|
+
}
|
|
10659
10867
|
}
|
|
10660
10868
|
var log3;
|
|
10661
10869
|
var init_server = __esm({
|