@vike-io/cli 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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/package.json +73 -0
- package/scripts/postinstall.js +20 -0
- package/skills/vike-alerts-setup/SKILL.md +75 -0
- package/skills/vike-alerts-webhook/SKILL.md +84 -0
- package/skills/vike-core/SKILL.md +67 -0
- package/skills/vike-cross-venue-perps/SKILL.md +52 -0
- package/skills/vike-defi-positions/SKILL.md +59 -0
- package/skills/vike-fund-holdings/SKILL.md +53 -0
- package/skills/vike-hl-top-traders/SKILL.md +52 -0
- package/skills/vike-hl-trader-profile/SKILL.md +65 -0
- package/skills/vike-options-flow/SKILL.md +49 -0
- package/skills/vike-perp-funding-arb/SKILL.md +53 -0
- package/skills/vike-perp-screener/SKILL.md +49 -0
- package/skills/vike-perp-spread-arb/SKILL.md +38 -0
- package/skills/vike-polymarket-market-detail/SKILL.md +42 -0
- package/skills/vike-polymarket-markets/SKILL.md +44 -0
- package/skills/vike-polymarket-screener/SKILL.md +59 -0
- package/skills/vike-polymarket-smart-money/SKILL.md +58 -0
- package/skills/vike-polymarket-trader/SKILL.md +55 -0
- package/skills/vike-router/SKILL.md +53 -0
- package/skills/vike-smart-money-discovery/SKILL.md +74 -0
- package/skills/vike-token-holders/SKILL.md +45 -0
- package/skills/vike-token-research/SKILL.md +55 -0
- package/skills/vike-token-search/SKILL.md +40 -0
- package/skills/vike-token-transfers/SKILL.md +43 -0
- package/skills/vike-wallet-balances/SKILL.md +60 -0
- package/skills/vike-wallet-counterparties/SKILL.md +48 -0
- package/skills/vike-wallet-discover/SKILL.md +49 -0
- package/skills/vike-wallet-ens/SKILL.md +41 -0
- package/skills/vike-wallet-label-provenance/SKILL.md +65 -0
- package/skills/vike-wallet-pnl-history/SKILL.md +58 -0
- package/skills/vike-wallet-portfolio/SKILL.md +61 -0
- package/skills/vike-wallet-summary/SKILL.md +35 -0
- package/skills/vike-web-fetch/SKILL.md +55 -0
- package/skills/vike-web-research/SKILL.md +58 -0
- package/skills/vike-web-search/SKILL.md +60 -0
- package/src/auth.js +55 -0
- package/src/commands/alerts.js +118 -0
- package/src/commands/auth.js +43 -0
- package/src/commands/doctor.js +87 -0
- package/src/commands/init.js +50 -0
- package/src/commands/labels.js +48 -0
- package/src/commands/options.js +24 -0
- package/src/commands/perp.js +59 -0
- package/src/commands/polymarket.js +74 -0
- package/src/commands/schema.js +31 -0
- package/src/commands/token.js +77 -0
- package/src/commands/wallet.js +106 -0
- package/src/commands/web.js +41 -0
- package/src/format.js +83 -0
- package/src/index.js +44 -0
- package/src/mcp-client.js +94 -0
- package/src/util.js +25 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerPerp(program) {
|
|
6
|
+
const cmd = program
|
|
7
|
+
.command('perp')
|
|
8
|
+
.description('Perpetual-futures funding, spreads, and Hyperliquid trader leaderboard');
|
|
9
|
+
|
|
10
|
+
cmd
|
|
11
|
+
.command('funding')
|
|
12
|
+
.description('Funding rates per venue, or cross-venue funding-arb opportunities')
|
|
13
|
+
.option('--symbol <s>', 'Specific symbol (e.g. BTC). Omit for top arb opportunities.')
|
|
14
|
+
.option('--json', 'Emit JSON')
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
try {
|
|
17
|
+
const r = await callTool('perp_funding', { symbol: opts.symbol });
|
|
18
|
+
emit(r, opts);
|
|
19
|
+
} catch (err) { emitError(err, opts); }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
cmd
|
|
23
|
+
.command('spreads')
|
|
24
|
+
.description('Cross-venue spot-perp spread arbitrage opportunities')
|
|
25
|
+
.option('--symbol <s>', 'Specific symbol. Omit for full matrix.')
|
|
26
|
+
.option('--json', 'Emit JSON')
|
|
27
|
+
.action(async (opts) => {
|
|
28
|
+
try {
|
|
29
|
+
const r = await callTool('perp_spreads', { symbol: opts.symbol });
|
|
30
|
+
emit(r, opts);
|
|
31
|
+
} catch (err) { emitError(err, opts); }
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
cmd
|
|
35
|
+
.command('top-traders')
|
|
36
|
+
.description('Top Hyperliquid perp wallets by PnL / ROI / win rate / volume')
|
|
37
|
+
.option('--window <w>', '1d | 7d | 30d | all', '30d')
|
|
38
|
+
.option('--sort <s>', 'pnl | roi | win_rate | volume | trade_count | drawdown', 'pnl')
|
|
39
|
+
.option('--order <o>', 'asc | desc', 'desc')
|
|
40
|
+
.option('--min-trades <n>', 'Minimum trade count', '3')
|
|
41
|
+
.option('--min-win-rate <p>', 'Minimum win rate percent (0-100)', '0')
|
|
42
|
+
.option('--min-volume <usd>', 'Minimum volume USD', '0')
|
|
43
|
+
.option('--size <n>', 'Page size (max 20)', '10')
|
|
44
|
+
.option('--json', 'Emit JSON')
|
|
45
|
+
.action(async (opts) => {
|
|
46
|
+
try {
|
|
47
|
+
const r = await callTool('hl_perp_top_traders', {
|
|
48
|
+
window: opts.window,
|
|
49
|
+
sort: opts.sort,
|
|
50
|
+
order: opts.order,
|
|
51
|
+
min_trades: parseInt10(opts.minTrades, 3),
|
|
52
|
+
min_win_rate: opts.minWinRate,
|
|
53
|
+
min_volume: opts.minVolume,
|
|
54
|
+
size: parseInt10(opts.size, 10),
|
|
55
|
+
});
|
|
56
|
+
emit(r, opts);
|
|
57
|
+
} catch (err) { emitError(err, opts); }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerPolymarket(program) {
|
|
6
|
+
const cmd = program
|
|
7
|
+
.command('polymarket')
|
|
8
|
+
.alias('pm')
|
|
9
|
+
.description('Polymarket prediction-market analytics (markets, traders, smart money)');
|
|
10
|
+
|
|
11
|
+
cmd
|
|
12
|
+
.command('markets')
|
|
13
|
+
.description('Trending Polymarket markets ranked by 24h volume')
|
|
14
|
+
.option('--category <c>', 'Crypto | Sports | Politics | Pop-Culture | Tech | all', 'all')
|
|
15
|
+
.option('--limit <n>', 'Max markets (max 30)', '10')
|
|
16
|
+
.option('--json', 'Emit JSON')
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
try {
|
|
19
|
+
const r = await callTool('polymarket_markets', {
|
|
20
|
+
category: opts.category,
|
|
21
|
+
limit: parseInt10(opts.limit, 10),
|
|
22
|
+
});
|
|
23
|
+
emit(r, opts);
|
|
24
|
+
} catch (err) { emitError(err, opts); }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
cmd
|
|
28
|
+
.command('market <condition_id>')
|
|
29
|
+
.description('Deep-dive on one Polymarket market by condition_id')
|
|
30
|
+
.option('--json', 'Emit JSON')
|
|
31
|
+
.action(async (conditionId, opts) => {
|
|
32
|
+
try {
|
|
33
|
+
const r = await callTool('polymarket_market_detail', {
|
|
34
|
+
condition_id: conditionId,
|
|
35
|
+
});
|
|
36
|
+
emit(r, opts);
|
|
37
|
+
} catch (err) { emitError(err, opts); }
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
cmd
|
|
41
|
+
.command('wallet <address>')
|
|
42
|
+
.description('Profile a Polymarket trader (PnL / hit-rate / categories)')
|
|
43
|
+
.option('--window <w>', '24h | 7d | 30d | all', 'all')
|
|
44
|
+
.option('--json', 'Emit JSON')
|
|
45
|
+
.action(async (address, opts) => {
|
|
46
|
+
try {
|
|
47
|
+
const r = await callTool('polymarket_wallet', {
|
|
48
|
+
address,
|
|
49
|
+
window: opts.window,
|
|
50
|
+
});
|
|
51
|
+
emit(r, opts);
|
|
52
|
+
} catch (err) { emitError(err, opts); }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
cmd
|
|
56
|
+
.command('smart-money')
|
|
57
|
+
.description('Smart-money flows or mispricings on Polymarket')
|
|
58
|
+
.option('--mode <m>', 'flows | mispricings', 'flows')
|
|
59
|
+
.option('--hours <n>', 'Lookback hours', '24')
|
|
60
|
+
.option('--category <c>', 'Crypto | Sports | Politics | Pop-Culture | Tech | all', 'all')
|
|
61
|
+
.option('--limit <n>', 'Max rows (max 20)', '10')
|
|
62
|
+
.option('--json', 'Emit JSON')
|
|
63
|
+
.action(async (opts) => {
|
|
64
|
+
try {
|
|
65
|
+
const r = await callTool('polymarket_smart_money', {
|
|
66
|
+
mode: opts.mode,
|
|
67
|
+
hours: parseInt10(opts.hours, 24),
|
|
68
|
+
category: opts.category,
|
|
69
|
+
limit: parseInt10(opts.limit, 10),
|
|
70
|
+
});
|
|
71
|
+
emit(r, opts);
|
|
72
|
+
} catch (err) { emitError(err, opts); }
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { listTools } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
|
|
4
|
+
export function registerSchema(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('schema')
|
|
7
|
+
.description('Print the full MCP tool schema (names, descriptions, input schemas)')
|
|
8
|
+
.option('--pretty', 'Human-friendly summary instead of raw JSON')
|
|
9
|
+
.option('--json', 'Emit JSON (default)')
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const tools = await listTools();
|
|
13
|
+
if (opts.pretty) {
|
|
14
|
+
for (const t of tools) {
|
|
15
|
+
console.log(`\n ${t.name}`);
|
|
16
|
+
console.log(` ${(t.description || '').split('\n')[0]}`);
|
|
17
|
+
const props = t.inputSchema?.properties ?? {};
|
|
18
|
+
const required = new Set(t.inputSchema?.required ?? []);
|
|
19
|
+
for (const [k, v] of Object.entries(props)) {
|
|
20
|
+
const tag = required.has(k) ? '(required)' : '';
|
|
21
|
+
const enumStr = Array.isArray(v.enum) ? ` [${v.enum.join('|')}]` : '';
|
|
22
|
+
console.log(` --${k}${enumStr} ${tag}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
console.log(`\n${tools.length} tools.`);
|
|
26
|
+
} else {
|
|
27
|
+
emit({ tools }, { json: true });
|
|
28
|
+
}
|
|
29
|
+
} catch (err) { emitError(err, opts); }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerToken(program) {
|
|
6
|
+
const cmd = program
|
|
7
|
+
.command('token')
|
|
8
|
+
.description('Token search, transfers, and price charts');
|
|
9
|
+
|
|
10
|
+
cmd
|
|
11
|
+
.command('search <query>')
|
|
12
|
+
.description('Search tokens by symbol or name')
|
|
13
|
+
.option('--chain <chain>', 'ethereum | bsc | base', 'ethereum')
|
|
14
|
+
.option('--limit <n>', 'Max results', '10')
|
|
15
|
+
.option('--json', 'Emit JSON')
|
|
16
|
+
.action(async (query, opts) => {
|
|
17
|
+
try {
|
|
18
|
+
const r = await callTool('token_search', {
|
|
19
|
+
query,
|
|
20
|
+
chain: opts.chain,
|
|
21
|
+
limit: parseInt10(opts.limit, 10),
|
|
22
|
+
});
|
|
23
|
+
emit(r, opts);
|
|
24
|
+
} catch (err) { emitError(err, opts); }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
cmd
|
|
28
|
+
.command('transfers <token_address>')
|
|
29
|
+
.description('Top buyers/sellers for a token in a window')
|
|
30
|
+
.option('--period <p>', '1h | 24h | 7d | 30d', '24h')
|
|
31
|
+
.option('--tab <t>', 'bought | sold', 'sold')
|
|
32
|
+
.option('--chain <chain>', 'ethereum | bsc | base | all', 'ethereum')
|
|
33
|
+
.option('--json', 'Emit JSON')
|
|
34
|
+
.action(async (tokenAddress, opts) => {
|
|
35
|
+
try {
|
|
36
|
+
const r = await callTool('token_transfers', {
|
|
37
|
+
token_address: tokenAddress,
|
|
38
|
+
period: opts.period,
|
|
39
|
+
tab: opts.tab,
|
|
40
|
+
chain: opts.chain,
|
|
41
|
+
});
|
|
42
|
+
emit(r, opts);
|
|
43
|
+
} catch (err) { emitError(err, opts); }
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
cmd
|
|
47
|
+
.command('chart <token_address>')
|
|
48
|
+
.description('OHLCV candle data for a token')
|
|
49
|
+
.option('--interval <m>', '5 | 30 | 120 minutes', '30')
|
|
50
|
+
.option('--json', 'Emit JSON')
|
|
51
|
+
.action(async (tokenAddress, opts) => {
|
|
52
|
+
try {
|
|
53
|
+
const r = await callTool('token_chart', {
|
|
54
|
+
token_address: tokenAddress,
|
|
55
|
+
interval: opts.interval,
|
|
56
|
+
});
|
|
57
|
+
emit(r, opts);
|
|
58
|
+
} catch (err) { emitError(err, opts); }
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
cmd
|
|
62
|
+
.command('holders <token_address>')
|
|
63
|
+
.description('Top holders of a token by cumulative net inflow USD')
|
|
64
|
+
.option('--chain <chain>', 'ethereum | bsc | base', 'ethereum')
|
|
65
|
+
.option('--limit <n>', 'Max holders (max 50)', '10')
|
|
66
|
+
.option('--json', 'Emit JSON')
|
|
67
|
+
.action(async (tokenAddress, opts) => {
|
|
68
|
+
try {
|
|
69
|
+
const r = await callTool('token_holders', {
|
|
70
|
+
token_address: tokenAddress,
|
|
71
|
+
chain: opts.chain,
|
|
72
|
+
limit: parseInt10(opts.limit, 10),
|
|
73
|
+
});
|
|
74
|
+
emit(r, opts);
|
|
75
|
+
} catch (err) { emitError(err, opts); }
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerWallet(program) {
|
|
6
|
+
const cmd = program
|
|
7
|
+
.command('wallet')
|
|
8
|
+
.description('Wallet metrics, discovery, and ranking');
|
|
9
|
+
|
|
10
|
+
cmd
|
|
11
|
+
.command('summary <address>')
|
|
12
|
+
.description('Aggregate metrics: volume / inflow / outflow / pnl across windows')
|
|
13
|
+
.option('--json', 'Emit JSON')
|
|
14
|
+
.action(async (address, opts) => {
|
|
15
|
+
try {
|
|
16
|
+
const r = await callTool('wallet_summary', { address });
|
|
17
|
+
emit(r, opts);
|
|
18
|
+
} catch (err) { emitError(err, opts); }
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
cmd
|
|
22
|
+
.command('counterparties <address>')
|
|
23
|
+
.description('Top trading partners of a wallet by combined transfer volume')
|
|
24
|
+
.option('--chain <c>', 'ethereum | bsc | base', 'ethereum')
|
|
25
|
+
.option('--window <w>', '24h | 7d | 30d | 90d | 180d | all', '30d')
|
|
26
|
+
.option('--limit <n>', 'Max counterparties (max 50)', '10')
|
|
27
|
+
.option('--json', 'Emit JSON')
|
|
28
|
+
.action(async (address, opts) => {
|
|
29
|
+
try {
|
|
30
|
+
const r = await callTool('wallet_counterparties', {
|
|
31
|
+
address,
|
|
32
|
+
chain: opts.chain,
|
|
33
|
+
window: opts.window,
|
|
34
|
+
limit: parseInt10(opts.limit, 10),
|
|
35
|
+
});
|
|
36
|
+
emit(r, opts);
|
|
37
|
+
} catch (err) { emitError(err, opts); }
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
cmd
|
|
41
|
+
.command('pnl-history <address>')
|
|
42
|
+
.description('Daily net-flow series (proxy for daily PnL)')
|
|
43
|
+
.option('--chain <c>', 'ethereum | bsc | base', 'ethereum')
|
|
44
|
+
.option('--days <n>', 'Days back (max 365)', '30')
|
|
45
|
+
.option('--json', 'Emit JSON')
|
|
46
|
+
.action(async (address, opts) => {
|
|
47
|
+
try {
|
|
48
|
+
const r = await callTool('wallet_pnl_history', {
|
|
49
|
+
address,
|
|
50
|
+
chain: opts.chain,
|
|
51
|
+
days: parseInt10(opts.days, 30),
|
|
52
|
+
});
|
|
53
|
+
emit(r, opts);
|
|
54
|
+
} catch (err) { emitError(err, opts); }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
cmd
|
|
58
|
+
.command('balances <address>')
|
|
59
|
+
.description('Current ERC-20 holdings across major EVM chains')
|
|
60
|
+
.option('--chains <list>', 'Comma-separated chains (eth,bsc,base,arbitrum,polygon,optimism)')
|
|
61
|
+
.option('--min-usd <n>', 'Filter dust below this USD value', '1')
|
|
62
|
+
.option('--json', 'Emit JSON')
|
|
63
|
+
.action(async (address, opts) => {
|
|
64
|
+
try {
|
|
65
|
+
const payload = { address, min_usd: opts.minUsd };
|
|
66
|
+
if (opts.chains) {
|
|
67
|
+
payload.chains = opts.chains.split(',').map((c) => c.trim()).filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
const r = await callTool('wallet_token_balances', payload);
|
|
70
|
+
emit(r, opts);
|
|
71
|
+
} catch (err) { emitError(err, opts); }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
cmd
|
|
75
|
+
.command('ens <address>')
|
|
76
|
+
.description('Reverse ENS lookup: address → vitalik.eth style name')
|
|
77
|
+
.option('--json', 'Emit JSON')
|
|
78
|
+
.action(async (address, opts) => {
|
|
79
|
+
try {
|
|
80
|
+
const r = await callTool('wallet_ens', { address });
|
|
81
|
+
emit(r, opts);
|
|
82
|
+
} catch (err) { emitError(err, opts); }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
cmd
|
|
86
|
+
.command('discover')
|
|
87
|
+
.description('Top wallets ranked by volume / inflow / outflow / pnl / net_flow / tx_count')
|
|
88
|
+
.option('--window <w>', '24h | 7d | 30d | 90d | all', '30d')
|
|
89
|
+
.option('--sort <s>', 'volume | inflow | outflow | net_flow | tx_count | pnl', 'volume')
|
|
90
|
+
.option('--order <o>', 'asc | desc', 'desc')
|
|
91
|
+
.option('--chain <c>', 'ethereum | bsc', 'ethereum')
|
|
92
|
+
.option('--size <n>', 'Page size (max 20)', '10')
|
|
93
|
+
.option('--json', 'Emit JSON')
|
|
94
|
+
.action(async (opts) => {
|
|
95
|
+
try {
|
|
96
|
+
const r = await callTool('wallet_discover', {
|
|
97
|
+
window: opts.window,
|
|
98
|
+
sort: opts.sort,
|
|
99
|
+
order: opts.order,
|
|
100
|
+
chain: opts.chain,
|
|
101
|
+
size: parseInt10(opts.size, 10),
|
|
102
|
+
});
|
|
103
|
+
emit(r, opts);
|
|
104
|
+
} catch (err) { emitError(err, opts); }
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerWeb(program) {
|
|
6
|
+
const cmd = program
|
|
7
|
+
.command('web')
|
|
8
|
+
.description('Web search + fetch (Tavily / Serper / Cerebras)');
|
|
9
|
+
|
|
10
|
+
cmd
|
|
11
|
+
.command('search <query...>')
|
|
12
|
+
.description('Web search (Tavily primary, Serper Google fallback)')
|
|
13
|
+
.option('--max <n>', 'Max results (default 5, max 10)', '5')
|
|
14
|
+
.option('--depth <d>', 'Tavily depth: basic | advanced', 'basic')
|
|
15
|
+
.option('--json', 'Emit JSON')
|
|
16
|
+
.action(async (queryParts, opts) => {
|
|
17
|
+
try {
|
|
18
|
+
const r = await callTool('web_search', {
|
|
19
|
+
query: queryParts.join(' '),
|
|
20
|
+
max_results: parseInt10(opts.max, 5),
|
|
21
|
+
search_depth: opts.depth,
|
|
22
|
+
});
|
|
23
|
+
emit(r, opts);
|
|
24
|
+
} catch (err) { emitError(err, opts); }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
cmd
|
|
28
|
+
.command('fetch <url>')
|
|
29
|
+
.description('Fetch URL and optionally answer a question about it')
|
|
30
|
+
.option('--ask <question>', 'Question to answer from the content (uses Cerebras)')
|
|
31
|
+
.option('--json', 'Emit JSON')
|
|
32
|
+
.action(async (url, opts) => {
|
|
33
|
+
try {
|
|
34
|
+
const r = await callTool('web_fetch', {
|
|
35
|
+
url,
|
|
36
|
+
question: opts.ask,
|
|
37
|
+
});
|
|
38
|
+
emit(r, opts);
|
|
39
|
+
} catch (err) { emitError(err, opts); }
|
|
40
|
+
});
|
|
41
|
+
}
|
package/src/format.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Output formatting — pretty-print for humans, JSON for pipes.
|
|
2
|
+
|
|
3
|
+
export function isJsonMode(opts) {
|
|
4
|
+
return Boolean(opts?.json) || !process.stdout.isTTY;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function emit(data, opts = {}) {
|
|
8
|
+
if (isJsonMode(opts)) {
|
|
9
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
// Human mode: light formatting for arrays of objects (most MCP responses).
|
|
13
|
+
if (Array.isArray(data)) {
|
|
14
|
+
if (data.length === 0) {
|
|
15
|
+
console.log('(no results)');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
printTable(data);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (data && typeof data === 'object') {
|
|
22
|
+
if (data.error) {
|
|
23
|
+
console.error(`error: ${data.error}`);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// If it has a single array prop, print that as table.
|
|
28
|
+
const arrayKey = Object.keys(data).find((k) => Array.isArray(data[k]));
|
|
29
|
+
if (arrayKey && Object.keys(data).length <= 3) {
|
|
30
|
+
printTable(data[arrayKey]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(JSON.stringify(data, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log(String(data));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printTable(rows) {
|
|
40
|
+
if (!rows.length) {
|
|
41
|
+
console.log('(empty)');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Collect a stable column order from the first row, then keep any
|
|
45
|
+
// new keys at the end. Truncate values >80 chars.
|
|
46
|
+
const cols = [];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
for (const k of Object.keys(row || {})) {
|
|
50
|
+
if (!seen.has(k)) { seen.add(k); cols.push(k); }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const widths = cols.map((c) => Math.min(40, Math.max(c.length, ...rows.map((r) => fmt(r?.[c]).length))));
|
|
54
|
+
const sep = ' ';
|
|
55
|
+
console.log(cols.map((c, i) => c.padEnd(widths[i])).join(sep));
|
|
56
|
+
console.log(widths.map((w) => '-'.repeat(w)).join(sep));
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
console.log(cols.map((c, i) => fmt(row?.[c]).padEnd(widths[i])).join(sep));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fmt(v) {
|
|
63
|
+
if (v === null || v === undefined) return '';
|
|
64
|
+
if (typeof v === 'number') {
|
|
65
|
+
if (Math.abs(v) >= 1e9) return (v / 1e9).toFixed(2) + 'B';
|
|
66
|
+
if (Math.abs(v) >= 1e6) return (v / 1e6).toFixed(2) + 'M';
|
|
67
|
+
if (Math.abs(v) >= 1e3) return (v / 1e3).toFixed(2) + 'K';
|
|
68
|
+
return String(v);
|
|
69
|
+
}
|
|
70
|
+
if (typeof v === 'object') return JSON.stringify(v);
|
|
71
|
+
const s = String(v);
|
|
72
|
+
return s.length > 80 ? s.slice(0, 77) + '...' : s;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function emitError(err, opts = {}) {
|
|
76
|
+
const msg = err?.message ?? String(err);
|
|
77
|
+
if (isJsonMode(opts)) {
|
|
78
|
+
process.stdout.write(JSON.stringify({ error: msg }) + '\n');
|
|
79
|
+
} else {
|
|
80
|
+
console.error(`vike: ${msg}`);
|
|
81
|
+
}
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerToken } from './commands/token.js';
|
|
4
|
+
import { registerWallet } from './commands/wallet.js';
|
|
5
|
+
import { registerPerp } from './commands/perp.js';
|
|
6
|
+
import { registerOptions } from './commands/options.js';
|
|
7
|
+
import { registerAlerts } from './commands/alerts.js';
|
|
8
|
+
import { registerPolymarket } from './commands/polymarket.js';
|
|
9
|
+
import { registerLabels } from './commands/labels.js';
|
|
10
|
+
import { registerWeb } from './commands/web.js';
|
|
11
|
+
// Note: registerLabels also registers `vike funds` and `vike defi` top-level
|
|
12
|
+
// commands. New wallet subcommands (counterparties, pnl-history, balances,
|
|
13
|
+
// ens) are added under registerWallet.
|
|
14
|
+
import { registerInit } from './commands/init.js';
|
|
15
|
+
import { registerDoctor } from './commands/doctor.js';
|
|
16
|
+
import { registerSchema } from './commands/schema.js';
|
|
17
|
+
import { registerLogin, registerLogout } from './commands/auth.js';
|
|
18
|
+
import { readPackageVersion } from './util.js';
|
|
19
|
+
|
|
20
|
+
const program = new Command();
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.name('vike')
|
|
24
|
+
.description('AI-agent CLI for vike.io on-chain analytics')
|
|
25
|
+
.version(readPackageVersion(), '-v, --version', 'Show CLI version');
|
|
26
|
+
|
|
27
|
+
registerLogin(program);
|
|
28
|
+
registerLogout(program);
|
|
29
|
+
registerInit(program);
|
|
30
|
+
registerDoctor(program);
|
|
31
|
+
registerSchema(program);
|
|
32
|
+
registerToken(program);
|
|
33
|
+
registerWallet(program);
|
|
34
|
+
registerPerp(program);
|
|
35
|
+
registerOptions(program);
|
|
36
|
+
registerAlerts(program);
|
|
37
|
+
registerPolymarket(program);
|
|
38
|
+
registerLabels(program);
|
|
39
|
+
registerWeb(program);
|
|
40
|
+
|
|
41
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
42
|
+
console.error(`vike: ${err.message ?? err}`);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { requireApiKey, getEndpoint, getApiKey } from './auth.js';
|
|
2
|
+
|
|
3
|
+
let _id = 0;
|
|
4
|
+
function nextId() { return ++_id; }
|
|
5
|
+
|
|
6
|
+
export async function callTool(toolName, args = {}, opts = {}) {
|
|
7
|
+
const apiKey = opts.apiKey ?? requireApiKey();
|
|
8
|
+
const endpoint = opts.endpoint ?? getEndpoint();
|
|
9
|
+
const timeoutMs = opts.timeoutMs ?? 60_000;
|
|
10
|
+
|
|
11
|
+
const body = {
|
|
12
|
+
jsonrpc: '2.0',
|
|
13
|
+
id: nextId(),
|
|
14
|
+
method: 'tools/call',
|
|
15
|
+
params: { name: toolName, arguments: args },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ctrl = new AbortController();
|
|
19
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
20
|
+
|
|
21
|
+
let resp;
|
|
22
|
+
try {
|
|
23
|
+
resp = await fetch(endpoint, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'X-API-KEY': apiKey,
|
|
28
|
+
'User-Agent': `vike-cli/${process.env.npm_package_version ?? 'dev'}`,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
signal: ctrl.signal,
|
|
32
|
+
});
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err.name === 'AbortError') {
|
|
35
|
+
throw new Error(`Request timed out after ${timeoutMs}ms`);
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Network error: ${err.message}`);
|
|
38
|
+
} finally {
|
|
39
|
+
clearTimeout(t);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const text = await resp.text();
|
|
43
|
+
let json;
|
|
44
|
+
try { json = JSON.parse(text); } catch {
|
|
45
|
+
throw new Error(`Bad response (HTTP ${resp.status}): ${text.slice(0, 200)}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (json.error) {
|
|
49
|
+
const msg = json.error.message ?? 'Unknown MCP error';
|
|
50
|
+
throw new Error(`MCP error ${json.error.code}: ${msg}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// tools/call returns { content: [{type:'text', text: JSON}], isError }
|
|
54
|
+
const content = json.result?.content?.[0]?.text;
|
|
55
|
+
if (!content) return json.result;
|
|
56
|
+
try { return JSON.parse(content); } catch {
|
|
57
|
+
return content;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function listTools(opts = {}) {
|
|
62
|
+
const apiKey = opts.apiKey ?? requireApiKey();
|
|
63
|
+
const endpoint = opts.endpoint ?? getEndpoint();
|
|
64
|
+
const body = {
|
|
65
|
+
jsonrpc: '2.0',
|
|
66
|
+
id: nextId(),
|
|
67
|
+
method: 'tools/list',
|
|
68
|
+
params: {},
|
|
69
|
+
};
|
|
70
|
+
const resp = await fetch(endpoint, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/json', 'X-API-KEY': apiKey },
|
|
73
|
+
body: JSON.stringify(body),
|
|
74
|
+
});
|
|
75
|
+
const json = await resp.json();
|
|
76
|
+
if (json.error) throw new Error(json.error.message);
|
|
77
|
+
return json.result?.tools ?? [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function ping(opts = {}) {
|
|
81
|
+
// initialize doesn't require auth — useful for `vike doctor`
|
|
82
|
+
const endpoint = opts.endpoint ?? getEndpoint();
|
|
83
|
+
const apiKey = opts.apiKey ?? getApiKey();
|
|
84
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
85
|
+
if (apiKey) headers['X-API-KEY'] = apiKey;
|
|
86
|
+
const resp = await fetch(endpoint, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers,
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
jsonrpc: '2.0', id: nextId(), method: 'initialize', params: {},
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
return { status: resp.status, body: await resp.json() };
|
|
94
|
+
}
|
package/src/util.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
export function readPackageVersion() {
|
|
9
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
10
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
11
|
+
return pkg.version;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseBool(v, fallback = false) {
|
|
15
|
+
if (v === undefined || v === null || v === '') return fallback;
|
|
16
|
+
if (typeof v === 'boolean') return v;
|
|
17
|
+
const s = String(v).toLowerCase();
|
|
18
|
+
return s === 'true' || s === '1' || s === 'yes' || s === 'y';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseInt10(v, fallback) {
|
|
22
|
+
if (v === undefined || v === null || v === '') return fallback;
|
|
23
|
+
const n = parseInt(v, 10);
|
|
24
|
+
return Number.isFinite(n) ? n : fallback;
|
|
25
|
+
}
|