@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,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vike-wallet-summary
|
|
3
|
+
description: Aggregate metrics for a single wallet — volume, inflow, outflow, net flow, tx count, tokens traded across 24h / 7d / 30d / 90d / all windows. Use when the user gives an address and asks "what's this wallet doing".
|
|
4
|
+
allowed-tools: Bash(vike:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# vike-wallet-summary
|
|
8
|
+
|
|
9
|
+
## Command
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vike wallet summary <address> [--json]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Output
|
|
16
|
+
|
|
17
|
+
Per-window rows: `window`, `volume_usd`, `inflow_usd`, `outflow_usd`, `net_flow_usd`, `tx_count`, `unique_tokens`.
|
|
18
|
+
|
|
19
|
+
## Reading tips
|
|
20
|
+
|
|
21
|
+
- **Net flow >> 0** = accumulator. Inflow heavy → still buying.
|
|
22
|
+
- **Net flow << 0** = distributor. Outflow heavy → still selling.
|
|
23
|
+
- **Volume high, net flow ≈ 0** = trader/MM (churn). Look at counterparty patterns separately.
|
|
24
|
+
- **Unique tokens high (>50)** = farmer / spray-and-pray. Lower signal per token.
|
|
25
|
+
- **Unique tokens low (<5), high volume** = concentrated conviction.
|
|
26
|
+
|
|
27
|
+
## Anti-patterns
|
|
28
|
+
|
|
29
|
+
- Don't claim a wallet is "smart money" based on summary alone — cross-reference with labels and PnL: `vike wallet discover --sort pnl` to see if this address is in the top-PnL set.
|
|
30
|
+
- A single big inflow can dominate `net_flow_usd` for a window — eyeball `tx_count` to gauge whether it's one trade or many.
|
|
31
|
+
|
|
32
|
+
## Pairs well with
|
|
33
|
+
|
|
34
|
+
- `vike wallet discover` to see if this wallet ranks in any leaderboard
|
|
35
|
+
- `vike token transfers` after identifying which tokens the wallet trades
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vike-web-fetch
|
|
3
|
+
description: Fetch a URL's content (via Tavily extract) and optionally answer a question about it using Cerebras Llama 3.3 70B. Use when the user gives a URL and asks "what does this page say" / "summarise X" / "extract Y from this article".
|
|
4
|
+
allowed-tools: Bash(vike:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# vike-web-fetch
|
|
8
|
+
|
|
9
|
+
## Command
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vike web fetch <url> [--ask "<question>"] [--json]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
vike web fetch https://example.com/article
|
|
19
|
+
vike web fetch https://example.com/article --ask "what's the launch date?"
|
|
20
|
+
vike web fetch https://docs.hyperliquid.xyz/funding --ask "how often does funding settle?"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Output
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"url": "https://...",
|
|
28
|
+
"content_length": 12345,
|
|
29
|
+
"content": "First 8K chars of extracted content...",
|
|
30
|
+
"answer": "Answer to your question if --ask was provided"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If `--ask` is omitted, you get raw extracted content only.
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
1. Tries Tavily's `/extract` endpoint first — returns clean markdown-like text
|
|
39
|
+
2. Falls back to raw HTTP GET if Tavily extract is unavailable
|
|
40
|
+
3. If `--ask` is provided, sends content + question to Cerebras Llama 3.3 70B for a focused answer
|
|
41
|
+
|
|
42
|
+
## Why we use Cerebras (not Gemini / OpenAI)
|
|
43
|
+
|
|
44
|
+
Cerebras Llama 3.3 70B is ~10x cheaper than Gemini 2.5 Flash and ~3-5x faster (1000+ tok/sec inference). Same answer quality for short Q&A on supplied content.
|
|
45
|
+
|
|
46
|
+
## Anti-patterns
|
|
47
|
+
|
|
48
|
+
- Don't fetch pay-walled URLs and expect text — you'll get a paywall page. Cite the URL and tell the user instead.
|
|
49
|
+
- Don't fetch huge files (PDFs > 10MB, videos) — the extract will be truncated and likely garbage.
|
|
50
|
+
- Don't ask `--ask` questions the content can't possibly answer — Cerebras will hallucinate. Stay tight to what's on the page.
|
|
51
|
+
|
|
52
|
+
## Pairs well with
|
|
53
|
+
|
|
54
|
+
- `vike web search` — find URLs to fetch first
|
|
55
|
+
- `vike-web-research` — composite multi-step research workflow
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vike-web-research
|
|
3
|
+
description: Multi-step web research playbook — search → fetch the most relevant URL → synthesize answer. Use when the user asks an open question that requires fresh web info ("what did Trump say about crypto yesterday", "latest exchange listing announcement", "summarize the latest Pendle update").
|
|
4
|
+
allowed-tools: Bash(vike:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# vike-web-research
|
|
8
|
+
|
|
9
|
+
Composite playbook combining `vike web search` and `vike web fetch` for cross-source synthesis.
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
### 1. Search (gather candidate URLs)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
vike web search "<query>" --max 5 --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Capture the top results' URLs and titles. If the response includes an `answer` field, that's often enough for simple factual queries — surface it directly and skip step 2.
|
|
20
|
+
|
|
21
|
+
### 2. Fetch (only when search snippets are insufficient)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
vike web fetch <top-result-url> --ask "<focused question>" --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Fetch only the highest-scoring (Tavily) or most-credible (Serper) URL. Re-fetching all 5 search results burns credits and adds noise.
|
|
28
|
+
|
|
29
|
+
### 3. Synthesize
|
|
30
|
+
|
|
31
|
+
> **Question:** `<user query>`
|
|
32
|
+
> **Answer (from `<source URL>`):** `<extracted answer>`
|
|
33
|
+
> **Other relevant sources:** `<url1>`, `<url2>` (with one-line summaries each)
|
|
34
|
+
|
|
35
|
+
Always link the source URL so the user can verify.
|
|
36
|
+
|
|
37
|
+
## When to skip the workflow
|
|
38
|
+
|
|
39
|
+
- **One-shot fact lookup** (price, date, contract address) → if `web search` returns it in the `answer` field, just quote that
|
|
40
|
+
- **The user already gave you a URL** → skip search, jump to `web fetch`
|
|
41
|
+
- **On-chain question** → don't web-research; use vike's on-chain tools (`token holders`, `wallet labels`, `perp funding`, etc.)
|
|
42
|
+
|
|
43
|
+
## Anti-patterns
|
|
44
|
+
|
|
45
|
+
- Don't fetch 5 URLs and average them — pick the most authoritative
|
|
46
|
+
- Don't quote Reddit / Twitter as primary sources for hard claims (price, dates, addresses); use them for sentiment only
|
|
47
|
+
- Don't combine web research with `vike-smart-money-discovery` results without saying so — they're different evidence types
|
|
48
|
+
|
|
49
|
+
## Cost
|
|
50
|
+
|
|
51
|
+
- Step 1: 2 credits (Tavily basic)
|
|
52
|
+
- Step 2: 3 credits (web_fetch with --ask = Tavily extract + Cerebras Q&A)
|
|
53
|
+
- Total: ~5 credits per research round
|
|
54
|
+
|
|
55
|
+
## Pairs well with
|
|
56
|
+
|
|
57
|
+
- `vike-token-research` — combine on-chain data with off-chain news
|
|
58
|
+
- `vike-polymarket-screener` — narrative-driven prediction markets often need news context
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vike-web-search
|
|
3
|
+
description: Web search using Tavily (LLM-tuned snippets, primary) with Serper Google fallback. Use when you need current events, project announcements, exchange listing news, or anything not in the vike on-chain databases.
|
|
4
|
+
allowed-tools: Bash(vike:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# vike-web-search
|
|
8
|
+
|
|
9
|
+
## Command
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vike web search "<query>" [--max N] [--depth basic|advanced] [--json]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The query can contain multiple words (no quoting required if you don't have shell-special chars).
|
|
16
|
+
|
|
17
|
+
## Examples
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
vike web search "hyperliquid funding rate explained"
|
|
21
|
+
vike web search "polymarket trump 2026 latest" --max 8
|
|
22
|
+
vike web search "ETH ETF inflows last week" --depth advanced
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Output
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"provider": "tavily",
|
|
30
|
+
"query": "...",
|
|
31
|
+
"answer": "Optional one-paragraph synthesised answer (Tavily only)",
|
|
32
|
+
"results": [
|
|
33
|
+
{ "title": "...", "url": "...", "snippet": "...", "score": 0.88 },
|
|
34
|
+
...
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`score` is a 0-1 relevance from Tavily (higher = more relevant). Serper doesn't include it.
|
|
40
|
+
|
|
41
|
+
## When to use vs on-chain tools
|
|
42
|
+
|
|
43
|
+
- **News / sentiment / project context** → `vike web search`
|
|
44
|
+
- **On-chain flows / wallet activity / token prices** → use the dedicated `vike token`/`vike wallet`/`vike perp` tools first
|
|
45
|
+
- **"What did X tweet" / "did Binance announce Y"** → web search
|
|
46
|
+
|
|
47
|
+
## Cost note
|
|
48
|
+
|
|
49
|
+
Web search calls cost 2 credits + an external API fee on our side. Use it when you genuinely need fresh / non-onchain info, not as a first-resort.
|
|
50
|
+
|
|
51
|
+
## Anti-patterns
|
|
52
|
+
|
|
53
|
+
- Don't web-search for things our on-chain tools answer faster ("top wallets holding PEPE" → `vike token holders` is faster, cheaper, and more accurate)
|
|
54
|
+
- Don't request `--max > 5` unless you genuinely need a wide net — agents waste tokens summarising 10 results when 3 would do
|
|
55
|
+
- `--depth advanced` doubles the cost; reserve for hard-to-answer queries
|
|
56
|
+
|
|
57
|
+
## Pairs well with
|
|
58
|
+
|
|
59
|
+
- `vike-web-fetch` — once you have a promising URL, fetch its full content
|
|
60
|
+
- `vike-web-research` — composite playbook for multi-step research
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.vike');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
export function getApiKey() {
|
|
9
|
+
// Resolution order: env var > config file
|
|
10
|
+
const envKey = process.env.VIKE_API_KEY;
|
|
11
|
+
if (envKey) return envKey;
|
|
12
|
+
|
|
13
|
+
if (!existsSync(CONFIG_FILE)) return null;
|
|
14
|
+
try {
|
|
15
|
+
const cfg = JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
16
|
+
return cfg.api_key || null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function saveApiKey(apiKey) {
|
|
23
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
24
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
25
|
+
}
|
|
26
|
+
let cfg = {};
|
|
27
|
+
if (existsSync(CONFIG_FILE)) {
|
|
28
|
+
try { cfg = JSON.parse(readFileSync(CONFIG_FILE, 'utf8')); } catch { /* ignore */ }
|
|
29
|
+
}
|
|
30
|
+
cfg.api_key = apiKey;
|
|
31
|
+
cfg.saved_at = new Date().toISOString();
|
|
32
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
33
|
+
return CONFIG_FILE;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function clearApiKey() {
|
|
37
|
+
if (!existsSync(CONFIG_FILE)) return false;
|
|
38
|
+
unlinkSync(CONFIG_FILE);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getEndpoint() {
|
|
43
|
+
return process.env.VIKE_MCP_URL || 'https://vike.io/mcp';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function requireApiKey() {
|
|
47
|
+
const key = getApiKey();
|
|
48
|
+
if (!key) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'No API key. Run `vike login --api-key <key>` or set VIKE_API_KEY. ' +
|
|
51
|
+
'Get a key at https://vike.io/api/keys',
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return key;
|
|
55
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseBool, parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerAlerts(program) {
|
|
6
|
+
const cmd = program
|
|
7
|
+
.command('alerts')
|
|
8
|
+
.description('Manage on-chain transfer alerts');
|
|
9
|
+
|
|
10
|
+
cmd
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('List your alerts')
|
|
13
|
+
.option('--include-inactive', 'Include paused/deleted alerts')
|
|
14
|
+
.option('--json', 'Emit JSON')
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
try {
|
|
17
|
+
const r = await callTool('alerts_list', {
|
|
18
|
+
include_inactive: parseBool(opts.includeInactive, false),
|
|
19
|
+
});
|
|
20
|
+
emit(r, opts);
|
|
21
|
+
} catch (err) { emitError(err, opts); }
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
cmd
|
|
25
|
+
.command('register-webhook <url>')
|
|
26
|
+
.description('Register an HTTPS webhook for alert delivery (HMAC-signed POST when secret given)')
|
|
27
|
+
.option('--secret <s>', 'Optional HMAC-SHA256 secret (recommended, >=16 chars)')
|
|
28
|
+
.option('--json', 'Emit JSON')
|
|
29
|
+
.action(async (url, opts) => {
|
|
30
|
+
try {
|
|
31
|
+
const r = await callTool('alerts_register_webhook', {
|
|
32
|
+
url,
|
|
33
|
+
secret: opts.secret,
|
|
34
|
+
});
|
|
35
|
+
emit(r, opts);
|
|
36
|
+
} catch (err) { emitError(err, opts); }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
cmd
|
|
40
|
+
.command('channels')
|
|
41
|
+
.description('List your enabled notification channels (email/discord/webhook)')
|
|
42
|
+
.option('--json', 'Emit JSON')
|
|
43
|
+
.action(async (opts) => {
|
|
44
|
+
try {
|
|
45
|
+
const r = await callTool('alerts_list_channels', {});
|
|
46
|
+
emit(r, opts);
|
|
47
|
+
} catch (err) { emitError(err, opts); }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
cmd
|
|
51
|
+
.command('create')
|
|
52
|
+
.description('Create a new on-chain transfer alert')
|
|
53
|
+
.requiredOption('--label <l>', 'Alert label (user-visible name)')
|
|
54
|
+
.option('--token <sym>', 'Token ticker, UPPERCASE (e.g. USDC, ETH)')
|
|
55
|
+
.option('--chain <c>', 'eth | bsc', 'eth')
|
|
56
|
+
.option('--min-usd <n>', 'Minimum transfer USD value')
|
|
57
|
+
.option('--max-usd <n>', 'Maximum transfer USD value')
|
|
58
|
+
.option('--channel <c...>', 'Notification channel(s): email, discord')
|
|
59
|
+
.option('--cooldown <min>', 'Minimum gap between triggers (minutes)', '60')
|
|
60
|
+
.option('--json', 'Emit JSON')
|
|
61
|
+
.action(async (opts) => {
|
|
62
|
+
try {
|
|
63
|
+
const r = await callTool('alerts_create', {
|
|
64
|
+
label: opts.label,
|
|
65
|
+
token_symbol: opts.token,
|
|
66
|
+
chain: opts.chain,
|
|
67
|
+
min_value_usd: opts.minUsd,
|
|
68
|
+
max_value_usd: opts.maxUsd,
|
|
69
|
+
channels: opts.channel,
|
|
70
|
+
cooldown_minutes: parseInt10(opts.cooldown, 60),
|
|
71
|
+
});
|
|
72
|
+
emit(r, opts);
|
|
73
|
+
} catch (err) { emitError(err, opts); }
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
cmd
|
|
77
|
+
.command('edit <alert_id>')
|
|
78
|
+
.description('Update an alert (only fields you pass are modified)')
|
|
79
|
+
.option('--label <l>')
|
|
80
|
+
.option('--token <sym>')
|
|
81
|
+
.option('--chain <c>')
|
|
82
|
+
.option('--min-usd <n>')
|
|
83
|
+
.option('--max-usd <n>')
|
|
84
|
+
.option('--channel <c...>')
|
|
85
|
+
.option('--cooldown <min>')
|
|
86
|
+
.option('--active <bool>', 'Pause/resume (true|false)')
|
|
87
|
+
.option('--json', 'Emit JSON')
|
|
88
|
+
.action(async (alertId, opts) => {
|
|
89
|
+
try {
|
|
90
|
+
const payload = { alert_id: alertId };
|
|
91
|
+
if (opts.label !== undefined) payload.label = opts.label;
|
|
92
|
+
if (opts.token !== undefined) payload.token_symbol = opts.token;
|
|
93
|
+
if (opts.chain !== undefined) payload.chain = opts.chain;
|
|
94
|
+
if (opts.minUsd !== undefined) payload.min_value_usd = opts.minUsd;
|
|
95
|
+
if (opts.maxUsd !== undefined) payload.max_value_usd = opts.maxUsd;
|
|
96
|
+
if (opts.channel !== undefined) payload.channels = opts.channel;
|
|
97
|
+
if (opts.cooldown !== undefined) payload.cooldown_minutes = opts.cooldown;
|
|
98
|
+
if (opts.active !== undefined) payload.is_active = parseBool(opts.active, true);
|
|
99
|
+
const r = await callTool('alerts_edit', payload);
|
|
100
|
+
emit(r, opts);
|
|
101
|
+
} catch (err) { emitError(err, opts); }
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
cmd
|
|
105
|
+
.command('delete <alert_id>')
|
|
106
|
+
.description('Delete (or pause) an alert')
|
|
107
|
+
.option('--hard', 'Hard-delete instead of pausing')
|
|
108
|
+
.option('--json', 'Emit JSON')
|
|
109
|
+
.action(async (alertId, opts) => {
|
|
110
|
+
try {
|
|
111
|
+
const r = await callTool('alerts_delete', {
|
|
112
|
+
alert_id: alertId,
|
|
113
|
+
hard: parseBool(opts.hard, false),
|
|
114
|
+
});
|
|
115
|
+
emit(r, opts);
|
|
116
|
+
} catch (err) { emitError(err, opts); }
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { saveApiKey, clearApiKey, getApiKey } from '../auth.js';
|
|
2
|
+
import { emit } from '../format.js';
|
|
3
|
+
|
|
4
|
+
export function registerLogin(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('login')
|
|
7
|
+
.description('Save an API key to ~/.vike/config.json')
|
|
8
|
+
.option('--api-key <key>', 'API key (vk_...)')
|
|
9
|
+
.action(async (opts) => {
|
|
10
|
+
if (!opts.apiKey) {
|
|
11
|
+
console.error('Pass --api-key <vk_...>. Get a key at https://vike.io/api/keys');
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const path = saveApiKey(opts.apiKey);
|
|
16
|
+
emit({ ok: true, saved_to: path });
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerLogout(program) {
|
|
21
|
+
program
|
|
22
|
+
.command('logout')
|
|
23
|
+
.description('Remove the saved API key')
|
|
24
|
+
.action(() => {
|
|
25
|
+
const cleared = clearApiKey();
|
|
26
|
+
emit({ ok: true, cleared });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function registerWhoami(program) {
|
|
31
|
+
program
|
|
32
|
+
.command('whoami')
|
|
33
|
+
.description('Show which API key is in use (truncated)')
|
|
34
|
+
.action(() => {
|
|
35
|
+
const k = getApiKey();
|
|
36
|
+
if (!k) {
|
|
37
|
+
emit({ authenticated: false });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const masked = k.length > 12 ? `${k.slice(0, 8)}...${k.slice(-4)}` : '***';
|
|
41
|
+
emit({ authenticated: true, api_key: masked, source: process.env.VIKE_API_KEY ? 'env' : 'config' });
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getApiKey, getEndpoint } from '../auth.js';
|
|
2
|
+
import { ping, listTools } from '../mcp-client.js';
|
|
3
|
+
|
|
4
|
+
export function registerDoctor(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('doctor')
|
|
7
|
+
.description('Diagnose CLI setup, endpoint reachability, and tool availability')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const checks = [];
|
|
10
|
+
|
|
11
|
+
// 1. Node version
|
|
12
|
+
const nodeVer = process.version;
|
|
13
|
+
const major = parseInt(nodeVer.slice(1).split('.')[0], 10);
|
|
14
|
+
checks.push({
|
|
15
|
+
name: 'Node runtime',
|
|
16
|
+
ok: major >= 20,
|
|
17
|
+
detail: `${nodeVer} (need >= 20.0.0)`,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// 2. API key
|
|
21
|
+
const key = getApiKey();
|
|
22
|
+
checks.push({
|
|
23
|
+
name: 'API key',
|
|
24
|
+
ok: Boolean(key),
|
|
25
|
+
detail: key
|
|
26
|
+
? `${key.slice(0, 8)}... (source: ${process.env.VIKE_API_KEY ? 'env' : 'config'})`
|
|
27
|
+
: 'Not set. Run `vike init` or `vike login --api-key <key>`',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 3. Endpoint
|
|
31
|
+
const endpoint = getEndpoint();
|
|
32
|
+
checks.push({
|
|
33
|
+
name: 'Endpoint URL',
|
|
34
|
+
ok: true,
|
|
35
|
+
detail: endpoint,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 4. Connectivity — HTTP 200 means endpoint up; auth error is separate concern
|
|
39
|
+
try {
|
|
40
|
+
const { status, body } = await ping({ apiKey: key ?? undefined });
|
|
41
|
+
const reachable = status === 200;
|
|
42
|
+
const authed = reachable && !body.error;
|
|
43
|
+
let detail;
|
|
44
|
+
if (authed) {
|
|
45
|
+
detail = `OK · server=${body?.result?.serverInfo?.name ?? '?'} v${body?.result?.serverInfo?.version ?? '?'}`;
|
|
46
|
+
} else if (reachable) {
|
|
47
|
+
detail = `Endpoint reachable; ${body?.error?.message ?? 'no auth'}`;
|
|
48
|
+
} else {
|
|
49
|
+
detail = `HTTP ${status} · ${JSON.stringify(body).slice(0, 200)}`;
|
|
50
|
+
}
|
|
51
|
+
checks.push({ name: 'MCP connectivity', ok: reachable, detail });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
checks.push({ name: 'MCP connectivity', ok: false, detail: err.message });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 5. Tool list (requires auth)
|
|
57
|
+
if (key) {
|
|
58
|
+
try {
|
|
59
|
+
const tools = await listTools();
|
|
60
|
+
checks.push({
|
|
61
|
+
name: 'Tool registry',
|
|
62
|
+
ok: tools.length > 0,
|
|
63
|
+
detail: `${tools.length} tools available`,
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
checks.push({ name: 'Tool registry', ok: false, detail: err.message });
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
checks.push({ name: 'Tool registry', ok: false, detail: 'Skipped (no API key)' });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Render
|
|
73
|
+
const PASS = '✔';
|
|
74
|
+
const FAIL = '✗';
|
|
75
|
+
for (const c of checks) {
|
|
76
|
+
const mark = c.ok ? PASS : FAIL;
|
|
77
|
+
console.log(`${mark} ${c.name.padEnd(20)} ${c.detail}`);
|
|
78
|
+
}
|
|
79
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
80
|
+
if (failed > 0) {
|
|
81
|
+
console.log(`\n${failed} check(s) failed.`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
} else {
|
|
84
|
+
console.log('\nAll checks passed. Try: vike token search BTC');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { saveApiKey, getApiKey } from '../auth.js';
|
|
2
|
+
import { ping } from '../mcp-client.js';
|
|
3
|
+
import { emit } from '../format.js';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { stdin, stdout } from 'node:process';
|
|
6
|
+
|
|
7
|
+
export function registerInit(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('init')
|
|
10
|
+
.description('Interactive setup: save your API key and verify connectivity')
|
|
11
|
+
.option('--api-key <key>', 'Skip the prompt by passing the key')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
let key = opts.apiKey ?? getApiKey();
|
|
14
|
+
|
|
15
|
+
if (!key) {
|
|
16
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
17
|
+
try {
|
|
18
|
+
console.log('Get your API key at https://vike.io/api/keys');
|
|
19
|
+
key = (await rl.question('Paste your API key (vk_...): ')).trim();
|
|
20
|
+
} finally {
|
|
21
|
+
rl.close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!key) {
|
|
26
|
+
console.error('No API key provided. Aborting.');
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const path = saveApiKey(key);
|
|
32
|
+
console.log(`Saved to ${path}`);
|
|
33
|
+
|
|
34
|
+
console.log('Verifying connection to vike.io/mcp ...');
|
|
35
|
+
const { status, body } = await ping({ apiKey: key });
|
|
36
|
+
if (status !== 200) {
|
|
37
|
+
console.error(`Endpoint returned HTTP ${status}: ${JSON.stringify(body)}`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const info = body?.result?.serverInfo ?? {};
|
|
42
|
+
emit({
|
|
43
|
+
ok: true,
|
|
44
|
+
server: info.name,
|
|
45
|
+
version: info.version,
|
|
46
|
+
protocol: body?.result?.protocolVersion,
|
|
47
|
+
next: 'Try: vike token search BTC',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
import { parseInt10 } from '../util.js';
|
|
4
|
+
|
|
5
|
+
export function registerLabels(program) {
|
|
6
|
+
// `vike labels <address>` — multi-source label provenance for one wallet
|
|
7
|
+
program
|
|
8
|
+
.command('labels <address>')
|
|
9
|
+
.description('Multi-source label cascade for a wallet (10+ providers + in-house heuristic + consensus view)')
|
|
10
|
+
.option('--json', 'Emit JSON')
|
|
11
|
+
.action(async (address, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
const r = await callTool('wallet_label_provenance', { address });
|
|
14
|
+
emit(r, opts);
|
|
15
|
+
} catch (err) { emitError(err, opts); }
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// `vike funds` — top labeled funds / MM / treasury / CEX by recent net inflow
|
|
19
|
+
program
|
|
20
|
+
.command('funds')
|
|
21
|
+
.description('Top labeled fund / market-maker / treasury / CEX wallets by net inflow')
|
|
22
|
+
.option('--kind <k>', 'funds | cex | mm | treasury', 'funds')
|
|
23
|
+
.option('--window <w>', '7d | 30d | 90d | 180d | all', '30d')
|
|
24
|
+
.option('--limit <n>', 'Max wallets (max 25)', '10')
|
|
25
|
+
.option('--json', 'Emit JSON')
|
|
26
|
+
.action(async (opts) => {
|
|
27
|
+
try {
|
|
28
|
+
const r = await callTool('fund_holdings', {
|
|
29
|
+
kind: opts.kind,
|
|
30
|
+
window: opts.window,
|
|
31
|
+
limit: parseInt10(opts.limit, 10),
|
|
32
|
+
});
|
|
33
|
+
emit(r, opts);
|
|
34
|
+
} catch (err) { emitError(err, opts); }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// `vike defi <address>` — DeBank protocol positions for a wallet
|
|
38
|
+
program
|
|
39
|
+
.command('defi <address>')
|
|
40
|
+
.description('DeFi protocol positions for a wallet across all DeBank-supported chains')
|
|
41
|
+
.option('--json', 'Emit JSON')
|
|
42
|
+
.action(async (address, opts) => {
|
|
43
|
+
try {
|
|
44
|
+
const r = await callTool('defi_positions', { address });
|
|
45
|
+
emit(r, opts);
|
|
46
|
+
} catch (err) { emitError(err, opts); }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { callTool } from '../mcp-client.js';
|
|
2
|
+
import { emit, emitError } from '../format.js';
|
|
3
|
+
|
|
4
|
+
export function registerOptions(program) {
|
|
5
|
+
const cmd = program
|
|
6
|
+
.command('options')
|
|
7
|
+
.description('Deribit options flow');
|
|
8
|
+
|
|
9
|
+
cmd
|
|
10
|
+
.command('flow')
|
|
11
|
+
.description('Call/put flow, volume, and put-call ratios')
|
|
12
|
+
.option('--symbol <s>', 'BTC | ETH | SOL', 'BTC')
|
|
13
|
+
.option('--window <w>', '1d | 1w | 1m', '1d')
|
|
14
|
+
.option('--json', 'Emit JSON')
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
try {
|
|
17
|
+
const r = await callTool('options_flow', {
|
|
18
|
+
symbol: opts.symbol,
|
|
19
|
+
window: opts.window,
|
|
20
|
+
});
|
|
21
|
+
emit(r, opts);
|
|
22
|
+
} catch (err) { emitError(err, opts); }
|
|
23
|
+
});
|
|
24
|
+
}
|