@darksol/terminal 0.1.1 → 0.2.1
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/package.json +1 -1
- package/skill/SKILL.md +177 -0
- package/src/cli.js +128 -0
- package/src/config/keys.js +320 -0
- package/src/llm/engine.js +286 -0
- package/src/llm/intent.js +310 -0
- package/src/services/skills.js +229 -0
- package/src/ui/banner.js +4 -2
- package/src/wallet/agent-signer.js +556 -0
package/package.json
CHANGED
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: darksol-terminal
|
|
3
|
+
description: "DARKSOL Terminal — unified CLI for trading, wallets, execution scripts, AI-powered market analysis, secure agent signing, and all DARKSOL services. Use when: (1) swapping/sniping tokens on Base/Ethereum/L2s, (2) managing encrypted wallets, (3) running automated trading scripts, (4) DCA strategies, (5) market intel lookups, (6) on-chain oracle/casino/cards, (7) natural language trading via LLM, (8) signing transactions securely for x402/contracts without exposing private keys."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DARKSOL Terminal
|
|
7
|
+
|
|
8
|
+
**All DARKSOL services. One terminal. Zero trust required. 🌑**
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @darksol/terminal
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Reference
|
|
17
|
+
|
|
18
|
+
### Wallet Management
|
|
19
|
+
```bash
|
|
20
|
+
darksol wallet create <name> # Create new wallet (AES-256-GCM encrypted)
|
|
21
|
+
darksol wallet import <name> # Import from private key
|
|
22
|
+
darksol wallet list # List all wallets
|
|
23
|
+
darksol wallet balance [name] # Check ETH + USDC balance
|
|
24
|
+
darksol wallet use <name> # Set active wallet
|
|
25
|
+
darksol wallet export [name] # Export details (password required for PK)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Trading
|
|
29
|
+
```bash
|
|
30
|
+
darksol trade swap -i ETH -o USDC -a 0.1 # Swap via Uniswap V3
|
|
31
|
+
darksol trade snipe <token> -a 0.05 # Fast buy with gas boost
|
|
32
|
+
darksol trade snipe <token> -a 0.05 -g 2.0 # Snipe with 2x gas priority
|
|
33
|
+
darksol trade watch # Monitor new pairs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### DCA (Dollar-Cost Averaging)
|
|
37
|
+
```bash
|
|
38
|
+
darksol dca create # Interactive DCA order creation
|
|
39
|
+
darksol dca list # List active DCA orders
|
|
40
|
+
darksol dca run # Execute pending orders
|
|
41
|
+
darksol dca cancel <id> # Cancel an order
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### AI Trading Assistant
|
|
45
|
+
```bash
|
|
46
|
+
darksol ai chat # Interactive AI trading chat
|
|
47
|
+
darksol ai ask "buy 0.5 ETH of AERO" # Parse natural language trade intent
|
|
48
|
+
darksol ai strategy VIRTUAL -b 500 # DCA strategy recommendation
|
|
49
|
+
darksol ai analyze AERO # AI-powered token analysis
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Execution Scripts
|
|
53
|
+
```bash
|
|
54
|
+
darksol script templates # List available templates
|
|
55
|
+
darksol script create # Create from template (buy, sell, limit-buy, stop-loss, etc.)
|
|
56
|
+
darksol script list # List saved scripts
|
|
57
|
+
darksol script run <name> # Execute (requires wallet password)
|
|
58
|
+
darksol script run <name> -p "pw" -y # Non-interactive (for automation/cron)
|
|
59
|
+
darksol script show <name> # View code + params
|
|
60
|
+
darksol script edit <name> # Edit params/wallet/chain
|
|
61
|
+
darksol script clone <name> <new> # Clone a script
|
|
62
|
+
darksol script delete <name> # Delete a script
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Script templates: `buy-token`, `sell-token`, `limit-buy`, `stop-loss`, `multi-buy`, `transfer`, `empty` (custom)
|
|
66
|
+
|
|
67
|
+
### Market Intel
|
|
68
|
+
```bash
|
|
69
|
+
darksol market top # Top movers on Base
|
|
70
|
+
darksol market top -c ethereum # Top movers on Ethereum
|
|
71
|
+
darksol market token VIRTUAL # Full token detail
|
|
72
|
+
darksol market compare ETH AERO VIRTUAL # Side-by-side comparison
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Secure Agent Signer (for OpenClaw / AI agents)
|
|
76
|
+
```bash
|
|
77
|
+
darksol agent start <wallet> # Start signing proxy
|
|
78
|
+
darksol agent start <wallet> --max-value 0.5 --daily-limit 2.0
|
|
79
|
+
darksol agent docs # Full security documentation
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The agent signer creates a local HTTP server at `127.0.0.1:18790` that signs transactions without exposing the private key. AI agents authenticate with a one-time bearer token.
|
|
83
|
+
|
|
84
|
+
**Endpoints:**
|
|
85
|
+
- `GET /address` — wallet address
|
|
86
|
+
- `GET /balance` — ETH balance
|
|
87
|
+
- `POST /send` — sign + broadcast transaction
|
|
88
|
+
- `POST /sign-message` — sign EIP-191 message (x402)
|
|
89
|
+
- `POST /sign-typed-data` — sign EIP-712 typed data (x402)
|
|
90
|
+
- `GET /policy` — spending limits
|
|
91
|
+
- `GET /audit` — operation log
|
|
92
|
+
|
|
93
|
+
### Oracle
|
|
94
|
+
```bash
|
|
95
|
+
darksol oracle flip # Coin flip
|
|
96
|
+
darksol oracle dice 20 # Roll d20
|
|
97
|
+
darksol oracle number 1 100 # Random 1-100
|
|
98
|
+
darksol oracle shuffle a b c d # Shuffle list
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Casino
|
|
102
|
+
```bash
|
|
103
|
+
darksol casino bet coin-flip heads # Place a bet
|
|
104
|
+
darksol casino tables # View games
|
|
105
|
+
darksol casino stats # House stats
|
|
106
|
+
darksol casino receipt <id> # Verify on-chain
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Prepaid Cards
|
|
110
|
+
```bash
|
|
111
|
+
darksol cards catalog # Available providers
|
|
112
|
+
darksol cards order -p swype -a 50 # Order a card
|
|
113
|
+
darksol cards status <id> # Check order
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Builder Index
|
|
117
|
+
```bash
|
|
118
|
+
darksol builders leaderboard # ERC-8021 builder rankings
|
|
119
|
+
darksol builders lookup <code> # Builder profile
|
|
120
|
+
darksol builders feed # Recent transactions
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Facilitator
|
|
124
|
+
```bash
|
|
125
|
+
darksol facilitator health # Status
|
|
126
|
+
darksol facilitator verify <payment> # Verify off-chain
|
|
127
|
+
darksol facilitator settle <payment> # Settle on-chain (free)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### API Keys
|
|
131
|
+
```bash
|
|
132
|
+
darksol keys list # Show all services + status
|
|
133
|
+
darksol keys add openai # Add OpenAI key
|
|
134
|
+
darksol keys add coingecko # Add CoinGecko Pro key
|
|
135
|
+
darksol keys add alchemy # Add Alchemy RPC key
|
|
136
|
+
darksol keys remove <service> # Remove a key
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Supported: `openai`, `anthropic`, `openrouter`, `ollama`, `coingecko`, `dexscreener`, `alchemy`, `infura`, `quicknode`, `oneinch`, `paraswap`
|
|
140
|
+
|
|
141
|
+
### Configuration
|
|
142
|
+
```bash
|
|
143
|
+
darksol config show # View all settings
|
|
144
|
+
darksol config set chain base # Set active chain
|
|
145
|
+
darksol config set slippage 1.0 # Set slippage %
|
|
146
|
+
darksol config rpc base https://... # Custom RPC endpoint
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Reference
|
|
150
|
+
```bash
|
|
151
|
+
darksol tips # Trading + scripting tips
|
|
152
|
+
darksol tips --trading # Trading tips only
|
|
153
|
+
darksol networks # Chain reference table
|
|
154
|
+
darksol quickstart # Getting started guide
|
|
155
|
+
darksol lookup 0x... # Look up address on-chain
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Supported Chains
|
|
159
|
+
- **Base** (default) — chain ID 8453
|
|
160
|
+
- **Ethereum** — chain ID 1
|
|
161
|
+
- **Polygon** — chain ID 137
|
|
162
|
+
- **Arbitrum** — chain ID 42161
|
|
163
|
+
- **Optimism** — chain ID 10
|
|
164
|
+
|
|
165
|
+
## Agent Integration Notes
|
|
166
|
+
|
|
167
|
+
- All commands work non-interactively with flags (`-p`, `-y`, `--key`, etc.)
|
|
168
|
+
- Set `darksol config set output json` for programmatic JSON responses
|
|
169
|
+
- Scripts can be executed via cron: `darksol script run my-dca -p "pass" -y`
|
|
170
|
+
- The agent signer is the recommended way to give AI agents wallet access
|
|
171
|
+
- Helper functions available at `@darksol/terminal/src/utils/helpers.js`
|
|
172
|
+
|
|
173
|
+
## Security
|
|
174
|
+
- Private keys encrypted with AES-256-GCM + scrypt KDF
|
|
175
|
+
- Agent signer: PK never exposed, loopback-only, bearer auth, spending limits
|
|
176
|
+
- Dangerous contract calls (transferOwnership, selfdestruct) blocked by default
|
|
177
|
+
- Full audit logging on all signing operations
|
package/src/cli.js
CHANGED
|
@@ -15,6 +15,10 @@ import { facilitatorHealth, facilitatorVerify, facilitatorSettle } from './servi
|
|
|
15
15
|
import { buildersLeaderboard, buildersLookup, buildersFeed } from './services/builders.js';
|
|
16
16
|
import { createScript, listScripts, runScript, showScript, editScript, deleteScript, cloneScript, listTemplates } from './scripts/engine.js';
|
|
17
17
|
import { showTradingTips, showScriptTips, showNetworkReference, showQuickStart, showWalletSummary, showTokenInfo, showTxResult } from './utils/helpers.js';
|
|
18
|
+
import { addKey, removeKey, listKeys } from './config/keys.js';
|
|
19
|
+
import { parseIntent, startChat, adviseStrategy, analyzeToken } from './llm/intent.js';
|
|
20
|
+
import { startAgentSigner, showAgentDocs } from './wallet/agent-signer.js';
|
|
21
|
+
import { listSkills, installSkill, skillInfo, uninstallSkill } from './services/skills.js';
|
|
18
22
|
|
|
19
23
|
export function cli(argv) {
|
|
20
24
|
const program = new Command();
|
|
@@ -289,6 +293,126 @@ export function cli(argv) {
|
|
|
289
293
|
.description('Settle payment on-chain')
|
|
290
294
|
.action((payment) => facilitatorSettle(payment));
|
|
291
295
|
|
|
296
|
+
// ═══════════════════════════════════════
|
|
297
|
+
// AI / LLM COMMANDS
|
|
298
|
+
// ═══════════════════════════════════════
|
|
299
|
+
const ai = program
|
|
300
|
+
.command('ai')
|
|
301
|
+
.description('AI-powered trading assistant & analysis');
|
|
302
|
+
|
|
303
|
+
ai
|
|
304
|
+
.command('chat')
|
|
305
|
+
.description('Start interactive AI trading chat')
|
|
306
|
+
.option('-p, --provider <name>', 'LLM provider (openai, anthropic, openrouter, ollama)')
|
|
307
|
+
.option('-m, --model <model>', 'Model name')
|
|
308
|
+
.action((opts) => startChat(opts));
|
|
309
|
+
|
|
310
|
+
ai
|
|
311
|
+
.command('ask <prompt...>')
|
|
312
|
+
.description('One-shot AI query')
|
|
313
|
+
.option('-p, --provider <name>', 'LLM provider')
|
|
314
|
+
.option('-m, --model <model>', 'Model name')
|
|
315
|
+
.action(async (promptParts, opts) => {
|
|
316
|
+
const prompt = promptParts.join(' ');
|
|
317
|
+
const result = await parseIntent(prompt, opts);
|
|
318
|
+
if (result.action !== 'error' && result.action !== 'unknown') {
|
|
319
|
+
showSection('PARSED INTENT');
|
|
320
|
+
kvDisplay(Object.entries(result)
|
|
321
|
+
.filter(([k]) => !['raw', 'model'].includes(k))
|
|
322
|
+
.map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : String(v)])
|
|
323
|
+
);
|
|
324
|
+
if (result.command) {
|
|
325
|
+
console.log('');
|
|
326
|
+
info(`Suggested command: ${theme.gold(result.command)}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
ai
|
|
332
|
+
.command('strategy <token>')
|
|
333
|
+
.description('Get DCA strategy recommendation')
|
|
334
|
+
.requiredOption('-b, --budget <usd>', 'Total budget in USD')
|
|
335
|
+
.option('-t, --timeframe <period>', 'Investment timeframe', '30 days')
|
|
336
|
+
.option('-p, --provider <name>', 'LLM provider')
|
|
337
|
+
.action((token, opts) => adviseStrategy(token, opts.budget, opts.timeframe, opts));
|
|
338
|
+
|
|
339
|
+
ai
|
|
340
|
+
.command('analyze <token>')
|
|
341
|
+
.description('AI-powered token analysis')
|
|
342
|
+
.option('-p, --provider <name>', 'LLM provider')
|
|
343
|
+
.action((token, opts) => analyzeToken(token, opts));
|
|
344
|
+
|
|
345
|
+
// ═══════════════════════════════════════
|
|
346
|
+
// API KEYS COMMANDS
|
|
347
|
+
// ═══════════════════════════════════════
|
|
348
|
+
const keys = program
|
|
349
|
+
.command('keys')
|
|
350
|
+
.description('API key vault — store keys for LLMs, data providers, RPCs');
|
|
351
|
+
|
|
352
|
+
keys
|
|
353
|
+
.command('list')
|
|
354
|
+
.description('List all services and stored keys')
|
|
355
|
+
.action(() => listKeys());
|
|
356
|
+
|
|
357
|
+
keys
|
|
358
|
+
.command('add <service>')
|
|
359
|
+
.description('Add or update an API key')
|
|
360
|
+
.option('-k, --key <key>', 'API key (or enter interactively)')
|
|
361
|
+
.action((service, opts) => addKey(service, opts));
|
|
362
|
+
|
|
363
|
+
keys
|
|
364
|
+
.command('remove <service>')
|
|
365
|
+
.description('Remove a stored key')
|
|
366
|
+
.action((service) => removeKey(service));
|
|
367
|
+
|
|
368
|
+
// ═══════════════════════════════════════
|
|
369
|
+
// AGENT SIGNER COMMANDS
|
|
370
|
+
// ═══════════════════════════════════════
|
|
371
|
+
const agent = program
|
|
372
|
+
.command('agent')
|
|
373
|
+
.description('Secure agent signer — PK-isolated wallet for AI agents');
|
|
374
|
+
|
|
375
|
+
agent
|
|
376
|
+
.command('start [wallet]')
|
|
377
|
+
.description('Start the agent signing proxy')
|
|
378
|
+
.option('--port <port>', 'Server port', '18790')
|
|
379
|
+
.option('--max-value <eth>', 'Max ETH per transaction', '1.0')
|
|
380
|
+
.option('--daily-limit <eth>', 'Daily spending limit in ETH', '5.0')
|
|
381
|
+
.option('--allowlist <contracts>', 'Comma-separated contract allowlist')
|
|
382
|
+
.action((wallet, opts) => startAgentSigner(wallet, opts));
|
|
383
|
+
|
|
384
|
+
agent
|
|
385
|
+
.command('docs')
|
|
386
|
+
.description('Show agent signer security documentation')
|
|
387
|
+
.action(() => showAgentDocs());
|
|
388
|
+
|
|
389
|
+
// ═══════════════════════════════════════
|
|
390
|
+
// SKILLS COMMANDS
|
|
391
|
+
// ═══════════════════════════════════════
|
|
392
|
+
const skills = program
|
|
393
|
+
.command('skills')
|
|
394
|
+
.description('DARKSOL skills directory — install agent skills');
|
|
395
|
+
|
|
396
|
+
skills
|
|
397
|
+
.command('list')
|
|
398
|
+
.description('List all available DARKSOL skills')
|
|
399
|
+
.action(() => listSkills());
|
|
400
|
+
|
|
401
|
+
skills
|
|
402
|
+
.command('install <name>')
|
|
403
|
+
.description('Install a skill to OpenClaw')
|
|
404
|
+
.action((name) => installSkill(name));
|
|
405
|
+
|
|
406
|
+
skills
|
|
407
|
+
.command('info <name>')
|
|
408
|
+
.description('Show skill details')
|
|
409
|
+
.action((name) => skillInfo(name));
|
|
410
|
+
|
|
411
|
+
skills
|
|
412
|
+
.command('uninstall <name>')
|
|
413
|
+
.description('Uninstall a skill')
|
|
414
|
+
.action((name) => uninstallSkill(name));
|
|
415
|
+
|
|
292
416
|
// ═══════════════════════════════════════
|
|
293
417
|
// TIPS & REFERENCE COMMANDS
|
|
294
418
|
// ═══════════════════════════════════════
|
|
@@ -466,6 +590,9 @@ export function cli(argv) {
|
|
|
466
590
|
['wallet', 'Create, import, manage wallets'],
|
|
467
591
|
['trade', 'Swap tokens, snipe, trading'],
|
|
468
592
|
['dca', 'Dollar-cost averaging orders'],
|
|
593
|
+
['ai', 'AI trading assistant & analysis'],
|
|
594
|
+
['agent', 'Secure agent signer (PK-isolated)'],
|
|
595
|
+
['keys', 'API key vault (LLMs, data, RPCs)'],
|
|
469
596
|
['script', 'Execution scripts & strategies'],
|
|
470
597
|
['market', 'Market intel & token data'],
|
|
471
598
|
['oracle', 'On-chain random oracle'],
|
|
@@ -473,6 +600,7 @@ export function cli(argv) {
|
|
|
473
600
|
['cards', 'Prepaid Visa/MC cards'],
|
|
474
601
|
['builders', 'ERC-8021 builder index'],
|
|
475
602
|
['facilitator', 'x402 payment facilitator'],
|
|
603
|
+
['skills', 'Agent skill directory & install'],
|
|
476
604
|
['config', 'Terminal configuration'],
|
|
477
605
|
['tips', 'Trading & scripting tips'],
|
|
478
606
|
['networks', 'Chain reference & explorers'],
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { theme } from '../ui/theme.js';
|
|
6
|
+
import { kvDisplay, success, error, warn, info } from '../ui/components.js';
|
|
7
|
+
import { showSection } from '../ui/banner.js';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
|
|
10
|
+
const KEYS_DIR = join(homedir(), '.darksol', 'keys');
|
|
11
|
+
const KEYS_FILE = join(KEYS_DIR, 'vault.json');
|
|
12
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
13
|
+
const SCRYPT_N = 2 ** 16; // lighter for keys (faster unlock)
|
|
14
|
+
const SCRYPT_r = 8;
|
|
15
|
+
const SCRYPT_p = 1;
|
|
16
|
+
const SCRYPT_MAXMEM = 512 * 1024 * 1024;
|
|
17
|
+
|
|
18
|
+
function ensureDir() {
|
|
19
|
+
if (!existsSync(KEYS_DIR)) mkdirSync(KEYS_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function encrypt(value, password) {
|
|
23
|
+
const salt = randomBytes(32);
|
|
24
|
+
const iv = randomBytes(16);
|
|
25
|
+
const key = scryptSync(password, salt, 32, { N: SCRYPT_N, r: SCRYPT_r, p: SCRYPT_p, maxmem: SCRYPT_MAXMEM });
|
|
26
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
27
|
+
let encrypted = cipher.update(value, 'utf8', 'hex');
|
|
28
|
+
encrypted += cipher.final('hex');
|
|
29
|
+
return {
|
|
30
|
+
salt: salt.toString('hex'),
|
|
31
|
+
iv: iv.toString('hex'),
|
|
32
|
+
tag: cipher.getAuthTag().toString('hex'),
|
|
33
|
+
data: encrypted,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function decrypt(entry, password) {
|
|
38
|
+
const key = scryptSync(password, Buffer.from(entry.salt, 'hex'), 32, { N: SCRYPT_N, r: SCRYPT_r, p: SCRYPT_p, maxmem: SCRYPT_MAXMEM });
|
|
39
|
+
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(entry.iv, 'hex'));
|
|
40
|
+
decipher.setAuthTag(Buffer.from(entry.tag, 'hex'));
|
|
41
|
+
let decrypted = decipher.update(entry.data, 'hex', 'utf8');
|
|
42
|
+
decrypted += decipher.final('utf8');
|
|
43
|
+
return decrypted;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadVault() {
|
|
47
|
+
ensureDir();
|
|
48
|
+
if (!existsSync(KEYS_FILE)) return { version: 1, keys: {} };
|
|
49
|
+
return JSON.parse(readFileSync(KEYS_FILE, 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function saveVault(vault) {
|
|
53
|
+
ensureDir();
|
|
54
|
+
writeFileSync(KEYS_FILE, JSON.stringify(vault, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ──────────────────────────────────────────────────
|
|
58
|
+
// SUPPORTED API SERVICES
|
|
59
|
+
// ──────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export const SERVICES = {
|
|
62
|
+
// LLM Providers
|
|
63
|
+
openai: {
|
|
64
|
+
name: 'OpenAI',
|
|
65
|
+
category: 'llm',
|
|
66
|
+
description: 'GPT-4o, GPT-5 — natural language trading, strategy advisor',
|
|
67
|
+
envVar: 'OPENAI_API_KEY',
|
|
68
|
+
docsUrl: 'https://platform.openai.com/api-keys',
|
|
69
|
+
validate: (key) => key.startsWith('sk-'),
|
|
70
|
+
},
|
|
71
|
+
anthropic: {
|
|
72
|
+
name: 'Anthropic',
|
|
73
|
+
category: 'llm',
|
|
74
|
+
description: 'Claude Opus, Sonnet — intent parsing, analysis',
|
|
75
|
+
envVar: 'ANTHROPIC_API_KEY',
|
|
76
|
+
docsUrl: 'https://console.anthropic.com/settings/keys',
|
|
77
|
+
validate: (key) => key.startsWith('sk-ant-'),
|
|
78
|
+
},
|
|
79
|
+
openrouter: {
|
|
80
|
+
name: 'OpenRouter',
|
|
81
|
+
category: 'llm',
|
|
82
|
+
description: 'Multi-model gateway — any LLM via one key',
|
|
83
|
+
envVar: 'OPENROUTER_API_KEY',
|
|
84
|
+
docsUrl: 'https://openrouter.ai/keys',
|
|
85
|
+
validate: (key) => key.startsWith('sk-or-'),
|
|
86
|
+
},
|
|
87
|
+
ollama: {
|
|
88
|
+
name: 'Ollama (Local)',
|
|
89
|
+
category: 'llm',
|
|
90
|
+
description: 'Local models — free, private, no API key needed',
|
|
91
|
+
envVar: 'OLLAMA_HOST',
|
|
92
|
+
docsUrl: 'https://ollama.ai',
|
|
93
|
+
validate: (key) => key.startsWith('http'),
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Data Providers
|
|
97
|
+
coingecko: {
|
|
98
|
+
name: 'CoinGecko Pro',
|
|
99
|
+
category: 'data',
|
|
100
|
+
description: 'Pro/Demo API — higher rate limits, more endpoints',
|
|
101
|
+
envVar: 'COINGECKO_API_KEY',
|
|
102
|
+
docsUrl: 'https://www.coingecko.com/en/api/pricing',
|
|
103
|
+
validate: (key) => key.length > 10,
|
|
104
|
+
},
|
|
105
|
+
dexscreener: {
|
|
106
|
+
name: 'DexScreener',
|
|
107
|
+
category: 'data',
|
|
108
|
+
description: 'Enhanced DEX data — paid tier for higher limits',
|
|
109
|
+
envVar: 'DEXSCREENER_API_KEY',
|
|
110
|
+
docsUrl: 'https://docs.dexscreener.com',
|
|
111
|
+
validate: (key) => key.length > 10,
|
|
112
|
+
},
|
|
113
|
+
defillama: {
|
|
114
|
+
name: 'DefiLlama',
|
|
115
|
+
category: 'data',
|
|
116
|
+
description: 'TVL, yield, protocol data — free, no key needed',
|
|
117
|
+
envVar: null,
|
|
118
|
+
docsUrl: 'https://defillama.com/docs/api',
|
|
119
|
+
validate: () => true,
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// RPC Providers (OAuth/API key)
|
|
123
|
+
alchemy: {
|
|
124
|
+
name: 'Alchemy',
|
|
125
|
+
category: 'rpc',
|
|
126
|
+
description: 'Premium RPC — faster, more reliable, trace APIs',
|
|
127
|
+
envVar: 'ALCHEMY_API_KEY',
|
|
128
|
+
docsUrl: 'https://dashboard.alchemy.com',
|
|
129
|
+
validate: (key) => key.length > 10,
|
|
130
|
+
},
|
|
131
|
+
infura: {
|
|
132
|
+
name: 'Infura',
|
|
133
|
+
category: 'rpc',
|
|
134
|
+
description: 'RPC provider — Ethereum, Polygon, Arbitrum, Optimism',
|
|
135
|
+
envVar: 'INFURA_API_KEY',
|
|
136
|
+
docsUrl: 'https://app.infura.io',
|
|
137
|
+
validate: (key) => key.length > 10,
|
|
138
|
+
},
|
|
139
|
+
quicknode: {
|
|
140
|
+
name: 'QuickNode',
|
|
141
|
+
category: 'rpc',
|
|
142
|
+
description: 'High-performance RPC — WebSocket support, trace',
|
|
143
|
+
envVar: 'QUICKNODE_API_KEY',
|
|
144
|
+
docsUrl: 'https://dashboard.quicknode.com',
|
|
145
|
+
validate: (key) => key.length > 10,
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Trading & Auth
|
|
149
|
+
oneinch: {
|
|
150
|
+
name: '1inch',
|
|
151
|
+
category: 'trading',
|
|
152
|
+
description: 'DEX aggregator API — best swap routing',
|
|
153
|
+
envVar: 'ONEINCH_API_KEY',
|
|
154
|
+
docsUrl: 'https://portal.1inch.dev',
|
|
155
|
+
validate: (key) => key.length > 10,
|
|
156
|
+
},
|
|
157
|
+
paraswap: {
|
|
158
|
+
name: 'ParaSwap',
|
|
159
|
+
category: 'trading',
|
|
160
|
+
description: 'DEX aggregator — competitive routing',
|
|
161
|
+
envVar: 'PARASWAP_API_KEY',
|
|
162
|
+
docsUrl: 'https://developers.paraswap.network',
|
|
163
|
+
validate: (key) => key.length > 5,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// ──────────────────────────────────────────────────
|
|
168
|
+
// KEY MANAGEMENT
|
|
169
|
+
// ──────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Add or update an API key
|
|
173
|
+
*/
|
|
174
|
+
export async function addKey(service, opts = {}) {
|
|
175
|
+
const svc = SERVICES[service];
|
|
176
|
+
if (!svc) {
|
|
177
|
+
error(`Unknown service: ${service}. Run: darksol keys list`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let apiKey = opts.key;
|
|
182
|
+
if (!apiKey) {
|
|
183
|
+
const { key } = await inquirer.prompt([{
|
|
184
|
+
type: 'password',
|
|
185
|
+
name: 'key',
|
|
186
|
+
message: theme.gold(`${svc.name} API key:`),
|
|
187
|
+
mask: '●',
|
|
188
|
+
validate: (v) => {
|
|
189
|
+
if (!v) return 'Key required';
|
|
190
|
+
if (svc.validate && !svc.validate(v)) return `Invalid format for ${svc.name}`;
|
|
191
|
+
return true;
|
|
192
|
+
},
|
|
193
|
+
}]);
|
|
194
|
+
apiKey = key;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Get vault password
|
|
198
|
+
let vaultPass = opts.password;
|
|
199
|
+
if (!vaultPass) {
|
|
200
|
+
const { password } = await inquirer.prompt([{
|
|
201
|
+
type: 'password',
|
|
202
|
+
name: 'password',
|
|
203
|
+
message: theme.gold('Vault password:'),
|
|
204
|
+
mask: '●',
|
|
205
|
+
validate: (v) => v.length >= 6 || 'Minimum 6 characters',
|
|
206
|
+
}]);
|
|
207
|
+
vaultPass = password;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const vault = loadVault();
|
|
211
|
+
vault.keys[service] = {
|
|
212
|
+
encrypted: encrypt(apiKey, vaultPass),
|
|
213
|
+
service: svc.name,
|
|
214
|
+
category: svc.category,
|
|
215
|
+
addedAt: new Date().toISOString(),
|
|
216
|
+
};
|
|
217
|
+
saveVault(vault);
|
|
218
|
+
|
|
219
|
+
success(`${svc.name} key stored securely`);
|
|
220
|
+
if (svc.envVar) {
|
|
221
|
+
info(`Also available via env: ${svc.envVar}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get a decrypted API key
|
|
227
|
+
*/
|
|
228
|
+
export async function getKey(service, password) {
|
|
229
|
+
const vault = loadVault();
|
|
230
|
+
const entry = vault.keys[service];
|
|
231
|
+
|
|
232
|
+
if (!entry) {
|
|
233
|
+
// Fall back to environment variable
|
|
234
|
+
const svc = SERVICES[service];
|
|
235
|
+
if (svc?.envVar && process.env[svc.envVar]) {
|
|
236
|
+
return process.env[svc.envVar];
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
return decrypt(entry.encrypted, password);
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get a key without password (tries env var first, then cached session)
|
|
250
|
+
*/
|
|
251
|
+
export function getKeyFromEnv(service) {
|
|
252
|
+
const svc = SERVICES[service];
|
|
253
|
+
if (svc?.envVar && process.env[svc.envVar]) {
|
|
254
|
+
return process.env[svc.envVar];
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Remove a key
|
|
261
|
+
*/
|
|
262
|
+
export async function removeKey(service) {
|
|
263
|
+
const vault = loadVault();
|
|
264
|
+
if (!vault.keys[service]) {
|
|
265
|
+
error(`No key stored for: ${service}`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const svc = SERVICES[service];
|
|
269
|
+
const { confirm } = await inquirer.prompt([{
|
|
270
|
+
type: 'confirm',
|
|
271
|
+
name: 'confirm',
|
|
272
|
+
message: theme.accent(`Remove ${svc?.name || service} key?`),
|
|
273
|
+
default: false,
|
|
274
|
+
}]);
|
|
275
|
+
if (!confirm) return;
|
|
276
|
+
|
|
277
|
+
delete vault.keys[service];
|
|
278
|
+
saveVault(vault);
|
|
279
|
+
success(`${svc?.name || service} key removed`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* List all services and stored keys
|
|
284
|
+
*/
|
|
285
|
+
export function listKeys() {
|
|
286
|
+
const vault = loadVault();
|
|
287
|
+
|
|
288
|
+
showSection('API KEY VAULT');
|
|
289
|
+
|
|
290
|
+
const categories = ['llm', 'data', 'rpc', 'trading'];
|
|
291
|
+
const catNames = { llm: '🧠 LLM PROVIDERS', data: '📊 DATA PROVIDERS', rpc: '🌐 RPC PROVIDERS', trading: '📈 TRADING' };
|
|
292
|
+
|
|
293
|
+
for (const cat of categories) {
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log(theme.gold(` ${catNames[cat]}`));
|
|
296
|
+
|
|
297
|
+
const services = Object.entries(SERVICES).filter(([, s]) => s.category === cat);
|
|
298
|
+
for (const [key, svc] of services) {
|
|
299
|
+
const stored = vault.keys[key];
|
|
300
|
+
const envKey = svc.envVar ? getKeyFromEnv(key) : null;
|
|
301
|
+
let status;
|
|
302
|
+
|
|
303
|
+
if (stored) {
|
|
304
|
+
status = theme.success('● Stored');
|
|
305
|
+
} else if (envKey) {
|
|
306
|
+
status = theme.info('● Env');
|
|
307
|
+
} else {
|
|
308
|
+
status = theme.dim('○ Not set');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log(` ${status} ${theme.label(svc.name.padEnd(18))} ${theme.dim(svc.description)}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log('');
|
|
316
|
+
info('Add a key: darksol keys add <service>');
|
|
317
|
+
info('Services: ' + Object.keys(SERVICES).join(', '));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export { KEYS_DIR, KEYS_FILE };
|