alpha-cli-toolkit 1.0.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/LICENSE +21 -0
- package/README.md +168 -0
- package/bin/alpha.js +365 -0
- package/package.json +49 -0
- package/utils/auth.js +108 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Linkxee-Tech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# 🧠 Alpha CLI — Autonomous Agent Onchain Toolkit
|
|
2
|
+
|
|
3
|
+
> A terminal-first autonomous agent for crypto & TradFi markets.
|
|
4
|
+
> Powered by **Nansen** (on-chain analytics) + **Yahoo Finance** (traditional markets) with **Phantom Wallet** authentication.
|
|
5
|
+
|
|
6
|
+
Built for the [Nansen CLI Build Challenge](https://nansen.ai).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
alpha-cli/
|
|
14
|
+
├── backend/ Express + MongoDB API server
|
|
15
|
+
│ ├── src/
|
|
16
|
+
│ │ ├── controllers/ Route handlers (auth, market, triggers)
|
|
17
|
+
│ │ ├── middleware/ JWT authentication guard
|
|
18
|
+
│ │ ├── models/ Mongoose schemas (User, Trigger, Position)
|
|
19
|
+
│ │ ├── routes/ Express route definitions
|
|
20
|
+
│ │ └── services/ Nansen API, Yahoo Finance, Trigger Engine
|
|
21
|
+
│ └── index.js Server entrypoint
|
|
22
|
+
├── cli/ Command-line interface
|
|
23
|
+
│ ├── bin/alpha.js CLI entrypoint (Commander.js)
|
|
24
|
+
│ └── utils/auth.js Phantom wallet auth flow via browser
|
|
25
|
+
└── (alpha-cli-ui/) Next.js Phantom Wallet authentication UI
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Data Flow
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
CLI (alpha login) → opens browser → Next.js UI → Phantom wallet sign
|
|
32
|
+
→ Backend verifies signature → returns JWT → CLI stores JWT locally
|
|
33
|
+
→ all subsequent CLI commands send JWT in Authorization header
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Setup
|
|
39
|
+
|
|
40
|
+
### Prerequisites
|
|
41
|
+
|
|
42
|
+
- **Node.js** ≥ 18
|
|
43
|
+
- **MongoDB** running locally (or a remote URI)
|
|
44
|
+
- **Phantom Wallet** browser extension
|
|
45
|
+
|
|
46
|
+
### 1. Backend
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd backend
|
|
50
|
+
cp .env.example .env # Edit with your MongoDB URI & secrets
|
|
51
|
+
npm install
|
|
52
|
+
npm run dev # Starts on http://localhost:4000
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Auth UI
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd ../alpha-cli-ui # Separate repo
|
|
59
|
+
npm install
|
|
60
|
+
npm run dev # Starts on http://localhost:3000
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. CLI
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cd ../cli
|
|
67
|
+
npm install
|
|
68
|
+
npm link # Makes `alpha` available globally
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## CLI Commands
|
|
74
|
+
|
|
75
|
+
### Authentication
|
|
76
|
+
|
|
77
|
+
| Command | Description |
|
|
78
|
+
|-----------------|-------------------------------------------|
|
|
79
|
+
| `alpha login` | Open browser → Phantom sign → store JWT |
|
|
80
|
+
| `alpha logout` | Clear stored authentication session |
|
|
81
|
+
| `alpha status` | Check auth status & backend health |
|
|
82
|
+
|
|
83
|
+
### Market Data
|
|
84
|
+
|
|
85
|
+
| Command | Description |
|
|
86
|
+
|------------------------|---------------------------------------------------|
|
|
87
|
+
| `alpha market` | Trending crypto via Nansen smart-money data |
|
|
88
|
+
| `alpha market --sp500` | Trending S&P 500 stocks via Yahoo Finance |
|
|
89
|
+
| `alpha market --nsd` | Trending NASDAQ stocks via Yahoo Finance |
|
|
90
|
+
| `alpha lookup <id>` | Deep dive into an asset (crypto or stock ticker) |
|
|
91
|
+
| `alpha watch <asset>` | Live-poll asset price every 30s (Ctrl+C to stop) |
|
|
92
|
+
|
|
93
|
+
### Paper Trading & Triggers
|
|
94
|
+
|
|
95
|
+
| Command | Description |
|
|
96
|
+
|------------------------------------------------------|------------------------------------------|
|
|
97
|
+
| `alpha positions` | View simulated portfolio & open positions |
|
|
98
|
+
| `alpha trigger buy <id> <condition> <target> <amt> <market>` | Set a BUY trigger |
|
|
99
|
+
| `alpha trigger sell <id> <condition> <target> <amt> <market>` | Set a SELL trigger |
|
|
100
|
+
| `alpha trigger list` | List all your triggers |
|
|
101
|
+
| `alpha trigger cancel <id>` | Cancel an active trigger |
|
|
102
|
+
|
|
103
|
+
**Condition types:** `PRICE_ABOVE`, `PRICE_BELOW`, `SMART_MONEY_INFLOW`, `EXCHANGE_OUTFLOW`
|
|
104
|
+
**Market types:** `CRYPTO`, `SP500`, `NASDAQ`
|
|
105
|
+
|
|
106
|
+
#### Example
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Buy 10 units of SOL when price drops below $140
|
|
110
|
+
alpha trigger buy SOL PRICE_BELOW 140 10 CRYPTO
|
|
111
|
+
|
|
112
|
+
# Sell 5 shares of AAPL when price goes above $200
|
|
113
|
+
alpha trigger sell AAPL PRICE_ABOVE 200 5 SP500
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Nansen API Integration
|
|
119
|
+
|
|
120
|
+
The backend integrates with **10 Nansen API endpoints** for on-chain analytics:
|
|
121
|
+
|
|
122
|
+
1. `GET /trending/tokens` — Hot contracts & trending tokens
|
|
123
|
+
2. `GET /smart-money/token-flows` — Smart money inflows/outflows
|
|
124
|
+
3. `GET /wallet/:address/balances` — Token balances for a wallet
|
|
125
|
+
4. `GET /token/:id/exchange-flows` — Exchange flow data
|
|
126
|
+
5. `GET /token/:id/holders` — Token holder distribution
|
|
127
|
+
6. `GET /smart-money/holdings` — Cross-chain smart money holdings
|
|
128
|
+
7. `GET /wallet/:address/profiler` — Wallet profiler
|
|
129
|
+
8. `GET /nft/indexes` — NFT market indexes
|
|
130
|
+
9. `GET /entities/:name/flows` — Entity-level token flow
|
|
131
|
+
10. `GET /token/:id/macro-signals` — Token God Mode macro signals
|
|
132
|
+
|
|
133
|
+
> **Note:** If `NANSEN_API_KEY` is not set in `.env`, the backend will return mock data so the CLI can be demonstrated without API access.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Autonomous Trigger Engine
|
|
138
|
+
|
|
139
|
+
The backend runs a **cron job every 60 seconds** that:
|
|
140
|
+
|
|
141
|
+
1. Fetches all `ACTIVE` triggers from MongoDB
|
|
142
|
+
2. For each trigger, fetches the live price/flow data from Nansen or Yahoo Finance
|
|
143
|
+
3. Evaluates the trigger condition (`PRICE_ABOVE`, `PRICE_BELOW`, etc.)
|
|
144
|
+
4. If the condition is met, executes a **simulated paper trade**:
|
|
145
|
+
- **BUY:** Deducts cost from `User.simulatedBalance`, creates/updates a `Position`
|
|
146
|
+
- **SELL:** Adds proceeds to `User.simulatedBalance`, reduces/removes a `Position`
|
|
147
|
+
5. Marks the trigger as `EXECUTED`
|
|
148
|
+
|
|
149
|
+
Each new user starts with a **$100,000 simulated paper-trading balance**.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Tech Stack
|
|
154
|
+
|
|
155
|
+
| Component | Technology |
|
|
156
|
+
|---------------|------------------------------------------------|
|
|
157
|
+
| CLI | Node.js, Commander.js, Chalk, cli-table3 |
|
|
158
|
+
| Backend | Express 5, MongoDB/Mongoose, JWT |
|
|
159
|
+
| Auth UI | Next.js 15, Solana wallet-adapter, Tailwind CSS |
|
|
160
|
+
| Market Data | Nansen API, Yahoo Finance (yahoo-finance2) |
|
|
161
|
+
| Auth | Phantom Wallet (Ed25519 signature + tweetnacl) |
|
|
162
|
+
| Scheduler | node-cron |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
package/bin/alpha.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require('commander');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const gradient = require('gradient-string');
|
|
5
|
+
const Table = require('cli-table3');
|
|
6
|
+
const { authenticateViaBrowser, getToken, clearToken } = require('../utils/auth');
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
const BACKEND_URL = process.env.ALPHA_BACKEND_URL || 'http://localhost:4000/api';
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('alpha')
|
|
13
|
+
.description('Alpha CLI – Autonomous Agent Onchain Toolkit (Powered by Nansen)')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
const checkAuth = () => {
|
|
17
|
+
const token = getToken();
|
|
18
|
+
if (!token) {
|
|
19
|
+
console.log(chalk.red('❌ Not authenticated. Please run `alpha login` first.'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
return token;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const authHeaders = (token) => ({ Authorization: `Bearer ${token}` });
|
|
26
|
+
|
|
27
|
+
const buildMarketTable = (items, cType) => {
|
|
28
|
+
const table = new Table({
|
|
29
|
+
head: [
|
|
30
|
+
chalk.bold.white('Asset'),
|
|
31
|
+
chalk.bold.white('Price'),
|
|
32
|
+
chalk.bold.white('Trend / Conviction'),
|
|
33
|
+
],
|
|
34
|
+
colWidths: [22, 16, 32],
|
|
35
|
+
style: { border: ['grey'] },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
items.forEach((asset) => {
|
|
39
|
+
const name = asset.name || asset.symbol;
|
|
40
|
+
const score = asset.trendScore ?? 0;
|
|
41
|
+
let colorizer = chalk.blue;
|
|
42
|
+
if (score >= 90) colorizer = chalk.green;
|
|
43
|
+
else if (score < 50) colorizer = chalk.yellow;
|
|
44
|
+
|
|
45
|
+
table.push([
|
|
46
|
+
colorizer(name),
|
|
47
|
+
chalk.white(`$${asset.price ?? '—'}`),
|
|
48
|
+
colorizer(`Score: ${score}`),
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log(gradient.atlas(`\n─── Trending ${cType} Assets ───────────────────`));
|
|
53
|
+
console.log(table.toString());
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.command('status')
|
|
58
|
+
.description('Check authentication status and backend health')
|
|
59
|
+
.action(async () => {
|
|
60
|
+
const axios = require('axios');
|
|
61
|
+
const token = getToken();
|
|
62
|
+
|
|
63
|
+
console.log(gradient.pastel('\n Alpha CLI – System Status\n'));
|
|
64
|
+
|
|
65
|
+
// Auth status
|
|
66
|
+
if (token) {
|
|
67
|
+
console.log(chalk.green(' ✅ Authenticated') + chalk.grey(' (JWT stored locally)'));
|
|
68
|
+
} else {
|
|
69
|
+
console.log(chalk.yellow(' ⚠️ Not authenticated') + chalk.grey(' — run `alpha login`'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Backend health
|
|
73
|
+
try {
|
|
74
|
+
const { data } = await axios.get(`http://localhost:4000/health`, { timeout: 3000 });
|
|
75
|
+
console.log(chalk.green(' ✅ Backend online') + chalk.grey(` (${data.timestamp})`));
|
|
76
|
+
} catch {
|
|
77
|
+
console.log(chalk.red(' ❌ Backend offline') + chalk.grey(' — start it with `npm run dev` in /backend'));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.command('login')
|
|
85
|
+
.description('Authenticate via Phantom Wallet through the browser')
|
|
86
|
+
.action(async () => {
|
|
87
|
+
try {
|
|
88
|
+
console.log(gradient.pastel.multiline('\nWelcome to Alpha CLI\nPreparing Web3 Authentication...\n'));
|
|
89
|
+
await authenticateViaBrowser();
|
|
90
|
+
console.log(chalk.green('\n✅ Successfully authenticated! Your session is stored securely.\n'));
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(chalk.red('\n❌ Authentication Failed:'), err.message);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
program
|
|
97
|
+
.command('logout')
|
|
98
|
+
.description('Clear the stored authentication session')
|
|
99
|
+
.action(() => {
|
|
100
|
+
clearToken();
|
|
101
|
+
console.log(chalk.yellow('\n👋 Logged out. Your JWT has been cleared.\n'));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
program
|
|
105
|
+
.command('market')
|
|
106
|
+
.description('Show trending assets powered by Nansen smart-money data')
|
|
107
|
+
.option('-c, --crypto', 'Trending crypto from Nansen (default)')
|
|
108
|
+
.option('-s, --sp500', 'Trending S&P 500 assets via Yahoo Finance')
|
|
109
|
+
.option('-n, --nsd', 'Trending NASDAQ assets via Yahoo Finance')
|
|
110
|
+
.action(async (options) => {
|
|
111
|
+
const token = checkAuth();
|
|
112
|
+
const axios = require('axios');
|
|
113
|
+
|
|
114
|
+
let url = `${BACKEND_URL}/market/crypto/trending`;
|
|
115
|
+
let cType = 'CRYPTO';
|
|
116
|
+
|
|
117
|
+
if (options.sp500) { url = `${BACKEND_URL}/market/tradfi/sp500`; cType = 'S&P 500'; }
|
|
118
|
+
else if (options.nsd) { url = `${BACKEND_URL}/market/tradfi/nsd`; cType = 'NASDAQ'; }
|
|
119
|
+
|
|
120
|
+
console.log(chalk.cyan(`\n🔍 Fetching ${cType} market data...`));
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const { data } = await axios.get(url, { headers: authHeaders(token) });
|
|
124
|
+
const items = data.data || data.assets || [];
|
|
125
|
+
|
|
126
|
+
if (!items.length) {
|
|
127
|
+
console.log(chalk.yellow('No data returned.'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
buildMarketTable(items, cType);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(chalk.red('Failed to fetch market data:'), err.response?.data?.error || err.message);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
program
|
|
138
|
+
.command('lookup <id>')
|
|
139
|
+
.description('Deep dive into an asset via Nansen or Yahoo Finance')
|
|
140
|
+
.action(async (id) => {
|
|
141
|
+
const token = checkAuth();
|
|
142
|
+
const axios = require('axios');
|
|
143
|
+
|
|
144
|
+
console.log(chalk.cyan(`\n🔍 Fetching deep insights for ${chalk.bold(id)}...`));
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const { data } = await axios.get(`${BACKEND_URL}/market/lookup/${id}`, { headers: authHeaders(token) });
|
|
148
|
+
|
|
149
|
+
if (data.type === 'tradfi') {
|
|
150
|
+
const q = data.data;
|
|
151
|
+
const table = new Table({ style: { border: ['grey'] } });
|
|
152
|
+
table.push(
|
|
153
|
+
[chalk.grey('Symbol'), chalk.white(q.symbol)],
|
|
154
|
+
[chalk.grey('Name'), chalk.white(q.longName || q.shortName || '—')],
|
|
155
|
+
[chalk.grey('Price'), chalk.green(`$${q.regularMarketPrice}`)],
|
|
156
|
+
[chalk.grey('Change %'), (q.regularMarketChangePercent >= 0 ? chalk.green : chalk.red)(`${q.regularMarketChangePercent?.toFixed(2)}%`)],
|
|
157
|
+
[chalk.grey('52W High'), chalk.white(`$${q.fiftyTwoWeekHigh}`)],
|
|
158
|
+
[chalk.grey('52W Low'), chalk.white(`$${q.fiftyTwoWeekLow}`)],
|
|
159
|
+
[chalk.grey('Market Cap'), chalk.white(q.marketCap ? `$${(q.marketCap / 1e9).toFixed(2)}B` : '—')],
|
|
160
|
+
);
|
|
161
|
+
console.log(gradient.atlas(`\n─── TradFi Insight: ${id.toUpperCase()} ─────────────`));
|
|
162
|
+
console.log(table.toString());
|
|
163
|
+
} else {
|
|
164
|
+
console.log(gradient.atlas(`\n─── Nansen Crypto Insight: ${id.toUpperCase()} ──────`));
|
|
165
|
+
console.log(JSON.stringify(data.nansenInsights, null, 2));
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error(chalk.red('Lookup failed:'), err.response?.data?.error || err.message);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
program
|
|
173
|
+
.command('positions')
|
|
174
|
+
.description('View your simulated portfolio and open positions')
|
|
175
|
+
.action(async () => {
|
|
176
|
+
const token = checkAuth();
|
|
177
|
+
const axios = require('axios');
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const { data } = await axios.get(`${BACKEND_URL}/triggers/portfolio`, { headers: authHeaders(token) });
|
|
181
|
+
|
|
182
|
+
console.log(gradient.pastel(`\n 💰 Simulated Balance: ${chalk.bold.green('$' + data.simulatedBalance?.toLocaleString())}\n`));
|
|
183
|
+
|
|
184
|
+
if (!data.positions?.length) {
|
|
185
|
+
console.log(chalk.grey(' No open positions. Use `alpha trigger buy` to start paper trading.\n'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const table = new Table({
|
|
190
|
+
head: [chalk.bold.white('Asset'), chalk.bold.white('Market'), chalk.bold.white('Amount'), chalk.bold.white('Avg Buy Price')],
|
|
191
|
+
colWidths: [18, 12, 14, 18],
|
|
192
|
+
style: { border: ['grey'] },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
data.positions.forEach((pos) => {
|
|
196
|
+
table.push([
|
|
197
|
+
chalk.cyan(pos.assetId),
|
|
198
|
+
chalk.white(pos.market),
|
|
199
|
+
chalk.white(pos.amount),
|
|
200
|
+
chalk.green(`$${pos.averageBuyPrice}`),
|
|
201
|
+
]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
console.log(chalk.bold(' 📊 Open Positions:'));
|
|
205
|
+
console.log(table.toString());
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error(chalk.red('Failed to fetch portfolio:'), err.response?.data?.error || err.message);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
program
|
|
212
|
+
.command('watch <asset>')
|
|
213
|
+
.description('Live-poll an asset every 30s (Ctrl+C to stop)')
|
|
214
|
+
.option('-i, --interval <seconds>', 'Polling interval in seconds', '30')
|
|
215
|
+
.action(async (asset, options) => {
|
|
216
|
+
const token = checkAuth();
|
|
217
|
+
const axios = require('axios');
|
|
218
|
+
const intervalMs = parseInt(options.interval, 10) * 1000;
|
|
219
|
+
|
|
220
|
+
console.log(gradient.atlas(`\n👁 Watching ${chalk.bold(asset)} every ${options.interval}s — Press Ctrl+C to stop\n`));
|
|
221
|
+
|
|
222
|
+
const fetch = async () => {
|
|
223
|
+
const ts = new Date().toLocaleTimeString();
|
|
224
|
+
try {
|
|
225
|
+
const { data } = await axios.get(`${BACKEND_URL}/market/lookup/${asset}`, { headers: authHeaders(token) });
|
|
226
|
+
|
|
227
|
+
if (data.type === 'tradfi') {
|
|
228
|
+
const q = data.data;
|
|
229
|
+
const change = q.regularMarketChangePercent?.toFixed(2);
|
|
230
|
+
const arrow = change >= 0 ? '▲' : '▼';
|
|
231
|
+
const colorizer = change >= 0 ? chalk.green : chalk.red;
|
|
232
|
+
console.log(
|
|
233
|
+
chalk.grey(`[${ts}]`) +
|
|
234
|
+
chalk.white(` ${q.symbol}`) +
|
|
235
|
+
` $${q.regularMarketPrice}` +
|
|
236
|
+
` ` + colorizer(`${arrow} ${change}%`)
|
|
237
|
+
);
|
|
238
|
+
} else {
|
|
239
|
+
const flows = data.nansenInsights?.flows;
|
|
240
|
+
console.log(chalk.grey(`[${ts}]`), chalk.white(asset.toUpperCase()), '—', chalk.cyan('Nansen data:'), JSON.stringify(flows ?? data.nansenInsights, null, 0));
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.log(chalk.grey(`[${ts}]`), chalk.red('Error:'), err.response?.data?.error || err.message);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
await fetch(); // Immediate first tick
|
|
248
|
+
setInterval(fetch, intervalMs);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const triggerCmd = program
|
|
252
|
+
.command('trigger')
|
|
253
|
+
.description('Manage autonomous smart-money paper-trading triggers');
|
|
254
|
+
|
|
255
|
+
// trigger buy
|
|
256
|
+
triggerCmd
|
|
257
|
+
.command('buy <id> <condition> <target> <amount> <market>')
|
|
258
|
+
.description('Set a BUY trigger. Example: alpha trigger buy SOL PRICE_BELOW 140 10 CRYPTO')
|
|
259
|
+
.action(async (id, condition, target, amount, market) => {
|
|
260
|
+
const token = checkAuth();
|
|
261
|
+
const axios = require('axios');
|
|
262
|
+
try {
|
|
263
|
+
const { data } = await axios.post(
|
|
264
|
+
`${BACKEND_URL}/triggers`,
|
|
265
|
+
{ assetId: id, type: 'BUY', conditionType: condition, targetValue: Number(target), amount: Number(amount), market: market.toUpperCase() },
|
|
266
|
+
{ headers: authHeaders(token) }
|
|
267
|
+
);
|
|
268
|
+
console.log(chalk.green(`\n✅ BUY trigger set! Agent will buy ${amount} ${id} when ${condition} reaches ${target}`));
|
|
269
|
+
console.log(chalk.grey(` Trigger ID: ${data.trigger._id}\n`));
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error(chalk.red('Failed to set trigger:'), err.response?.data?.error || err.message);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// trigger sell
|
|
276
|
+
triggerCmd
|
|
277
|
+
.command('sell <id> <condition> <target> <amount> <market>')
|
|
278
|
+
.description('Set a SELL trigger. Example: alpha trigger sell SOL PRICE_ABOVE 200 10 CRYPTO')
|
|
279
|
+
.action(async (id, condition, target, amount, market) => {
|
|
280
|
+
const token = checkAuth();
|
|
281
|
+
const axios = require('axios');
|
|
282
|
+
try {
|
|
283
|
+
const { data } = await axios.post(
|
|
284
|
+
`${BACKEND_URL}/triggers`,
|
|
285
|
+
{ assetId: id, type: 'SELL', conditionType: condition, targetValue: Number(target), amount: Number(amount), market: market.toUpperCase() },
|
|
286
|
+
{ headers: authHeaders(token) }
|
|
287
|
+
);
|
|
288
|
+
console.log(chalk.green(`\n✅ SELL trigger set! Agent will sell ${amount} ${id} when ${condition} reaches ${target}`));
|
|
289
|
+
console.log(chalk.grey(` Trigger ID: ${data.trigger._id}\n`));
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error(chalk.red('Failed to set trigger:'), err.response?.data?.error || err.message);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// trigger list
|
|
296
|
+
triggerCmd
|
|
297
|
+
.command('list')
|
|
298
|
+
.description('List all your active and past triggers')
|
|
299
|
+
.action(async () => {
|
|
300
|
+
const token = checkAuth();
|
|
301
|
+
const axios = require('axios');
|
|
302
|
+
try {
|
|
303
|
+
const { data } = await axios.get(`${BACKEND_URL}/triggers`, { headers: authHeaders(token) });
|
|
304
|
+
|
|
305
|
+
if (!data.triggers?.length) {
|
|
306
|
+
console.log(chalk.yellow('\n No triggers found. Use `alpha trigger buy` or `alpha trigger sell` to create one.\n'));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const table = new Table({
|
|
311
|
+
head: [
|
|
312
|
+
chalk.bold.white('ID'),
|
|
313
|
+
chalk.bold.white('Asset'),
|
|
314
|
+
chalk.bold.white('Type'),
|
|
315
|
+
chalk.bold.white('Condition'),
|
|
316
|
+
chalk.bold.white('Target'),
|
|
317
|
+
chalk.bold.white('Amount'),
|
|
318
|
+
chalk.bold.white('Market'),
|
|
319
|
+
chalk.bold.white('Status'),
|
|
320
|
+
],
|
|
321
|
+
colWidths: [28, 12, 8, 22, 12, 10, 10, 12],
|
|
322
|
+
style: { border: ['grey'] },
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
data.triggers.forEach((t) => {
|
|
326
|
+
const statusColor =
|
|
327
|
+
t.status === 'ACTIVE' ? chalk.green :
|
|
328
|
+
t.status === 'EXECUTED' ? chalk.blue : chalk.red;
|
|
329
|
+
|
|
330
|
+
table.push([
|
|
331
|
+
chalk.grey(t._id),
|
|
332
|
+
chalk.cyan(t.assetId),
|
|
333
|
+
t.type === 'BUY' ? chalk.green(t.type) : chalk.red(t.type),
|
|
334
|
+
chalk.white(t.conditionType),
|
|
335
|
+
chalk.white(t.targetValue),
|
|
336
|
+
chalk.white(t.amount),
|
|
337
|
+
chalk.white(t.market),
|
|
338
|
+
statusColor(t.status),
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
console.log(gradient.atlas('\n─── Triggers ──────────────────────────────'));
|
|
343
|
+
console.log(table.toString());
|
|
344
|
+
console.log('');
|
|
345
|
+
} catch (err) {
|
|
346
|
+
console.error(chalk.red('Failed to fetch triggers:'), err.response?.data?.error || err.message);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// trigger cancel
|
|
351
|
+
triggerCmd
|
|
352
|
+
.command('cancel <id>')
|
|
353
|
+
.description('Cancel an active trigger by its ID')
|
|
354
|
+
.action(async (id) => {
|
|
355
|
+
const token = checkAuth();
|
|
356
|
+
const axios = require('axios');
|
|
357
|
+
try {
|
|
358
|
+
await axios.delete(`${BACKEND_URL}/triggers/${id}`, { headers: authHeaders(token) });
|
|
359
|
+
console.log(chalk.yellow(`\n⛔ Trigger ${chalk.bold(id)} has been cancelled.\n`));
|
|
360
|
+
} catch (err) {
|
|
361
|
+
console.error(chalk.red('Failed to cancel trigger:'), err.response?.data?.error || err.message);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alpha-cli-toolkit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Autonomous Agent Toolkit for Crypto & TradFi market data powered by Nansen.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node bin/alpha.js",
|
|
8
|
+
"dev": "node bin/alpha.js --help",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"alpha": "bin/alpha.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"utils",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"crypto",
|
|
22
|
+
"nansen",
|
|
23
|
+
"tradfi",
|
|
24
|
+
"autonomous-agent",
|
|
25
|
+
"cli-toolkit"
|
|
26
|
+
],
|
|
27
|
+
"author": "Linkxee-Tech",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Linkxee-Tech/alpha-cli.git"
|
|
32
|
+
},
|
|
33
|
+
"type": "commonjs",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"axios": "^1.14.0",
|
|
36
|
+
"chalk": "^4.1.2",
|
|
37
|
+
"cli-table3": "^0.6.5",
|
|
38
|
+
"commander": "^14.0.3",
|
|
39
|
+
"dotenv": "^17.3.1",
|
|
40
|
+
"gradient-string": "^2.0.2",
|
|
41
|
+
"inquirer": "^13.3.2",
|
|
42
|
+
"node-cron": "^4.2.1",
|
|
43
|
+
"open": "^11.0.0",
|
|
44
|
+
"ora": "^5.4.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"nodemon": "^3.1.14"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/utils/auth.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const url = require('url');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
// ─── Simple config store (replaces `conf` to avoid ESM issues) ───────────────
|
|
8
|
+
const CONFIG_DIR = path.join(os.homedir(), '.alpha-cli');
|
|
9
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
10
|
+
|
|
11
|
+
function readConfig() {
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(CONFIG_FILE)) return {};
|
|
14
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeConfig(data) {
|
|
21
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
22
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Token management ────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function getToken() {
|
|
30
|
+
return readConfig().jwt || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function clearToken() {
|
|
34
|
+
const config = readConfig();
|
|
35
|
+
delete config.jwt;
|
|
36
|
+
writeConfig(config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function setToken(token) {
|
|
40
|
+
const config = readConfig();
|
|
41
|
+
config.jwt = token;
|
|
42
|
+
writeConfig(config);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Browser-based Phantom auth flow ─────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Spins up a local temporary server and opens the Next.js UI
|
|
49
|
+
* to capture the Phantom wallet authentication token.
|
|
50
|
+
*/
|
|
51
|
+
async function authenticateViaBrowser() {
|
|
52
|
+
return new Promise(async (resolve, reject) => {
|
|
53
|
+
const server = http.createServer((req, res) => {
|
|
54
|
+
const parsedUrl = url.parse(req.url, true);
|
|
55
|
+
|
|
56
|
+
if (parsedUrl.pathname === '/') {
|
|
57
|
+
const token = parsedUrl.query.token;
|
|
58
|
+
|
|
59
|
+
if (token) {
|
|
60
|
+
setToken(token);
|
|
61
|
+
|
|
62
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
63
|
+
res.end(`
|
|
64
|
+
<html><body style="background: #0a0a1a; color: #4ade80; font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0;">
|
|
65
|
+
<div style="text-align: center;">
|
|
66
|
+
<h1 style="font-size: 2rem; margin-bottom: 0.5rem;">✓ Authentication Complete</h1>
|
|
67
|
+
<p style="color: #666;">You can close this tab and return to your terminal.</p>
|
|
68
|
+
</div>
|
|
69
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
70
|
+
</body></html>
|
|
71
|
+
`);
|
|
72
|
+
|
|
73
|
+
server.close();
|
|
74
|
+
resolve(token);
|
|
75
|
+
} else {
|
|
76
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
77
|
+
res.end('<h1>Failed to authenticate. No token returned.</h1>');
|
|
78
|
+
server.close();
|
|
79
|
+
reject(new Error('No token returned'));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
server.listen(0, async () => {
|
|
85
|
+
const port = server.address().port;
|
|
86
|
+
const callbackUrl = `http://localhost:${port}`;
|
|
87
|
+
const loginUrl = `http://localhost:3000/?callback=${encodeURIComponent(callbackUrl)}`;
|
|
88
|
+
|
|
89
|
+
// Dynamic import for `open` (ESM-only since v9)
|
|
90
|
+
const open = (await import('open')).default;
|
|
91
|
+
|
|
92
|
+
console.log(`Opening browser to authenticate via Phantom Wallet...`);
|
|
93
|
+
await open(loginUrl);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Timeout after 5 minutes
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
server.close();
|
|
99
|
+
reject(new Error('Authentication timed out after 5 minutes'));
|
|
100
|
+
}, 5 * 60 * 1000);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
authenticateViaBrowser,
|
|
106
|
+
getToken,
|
|
107
|
+
clearToken,
|
|
108
|
+
};
|