@glide-pool/cli 0.1.0 → 0.1.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/bin/glidepool +311 -0
- package/package.json +2 -3
package/bin/glidepool
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const pkg = require('../package.json');
|
|
11
|
+
|
|
12
|
+
// ─────────────────────────────────────────
|
|
13
|
+
// Config
|
|
14
|
+
// ─────────────────────────────────────────
|
|
15
|
+
const CONFIG_DIR = join(homedir(), '.glidepool');
|
|
16
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
17
|
+
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
20
|
+
try { return JSON.parse(readFileSync(CONFIG_FILE, 'utf8')); } catch { return {}; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function saveConfig(data) {
|
|
24
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getApiUrl(opts) {
|
|
29
|
+
const url = opts.api || process.env.GLIDEPOOL_API_URL || loadConfig().apiUrl;
|
|
30
|
+
if (!url) {
|
|
31
|
+
console.error('Error: API URL required. Set via --api <url> or GLIDEPOOL_API_URL env or run: glidepool config set-api <url>');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
return url.replace(/\/$/, '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────
|
|
38
|
+
// HTTP helper
|
|
39
|
+
// ─────────────────────────────────────────
|
|
40
|
+
async function apiFetch(apiUrl, path, options = {}) {
|
|
41
|
+
const url = `${apiUrl}${path}`;
|
|
42
|
+
const res = await fetch(url, {
|
|
43
|
+
...options,
|
|
44
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
45
|
+
});
|
|
46
|
+
const data = await res.json().catch(() => ({}));
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
if (res.status === 402) {
|
|
49
|
+
console.error('\n[402] Payment Required');
|
|
50
|
+
console.error(` Send ${data.amount || '0.05'} ${data.currency || 'USDC'} on Base to:`);
|
|
51
|
+
console.error(` ${data.recipient}`);
|
|
52
|
+
console.error(` Token: ${data.token}`);
|
|
53
|
+
console.error(`\n Then retry with: --payment-proof <base64(JSON{txHash,from,amount})>`);
|
|
54
|
+
process.exit(2);
|
|
55
|
+
}
|
|
56
|
+
console.error(`API error (${res.status}): ${data.message || data.error || 'Unknown error'}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function printJson(data) {
|
|
63
|
+
console.log(JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function printTable(rows, columns) {
|
|
67
|
+
const widths = columns.map(c => Math.max(c.label.length, ...rows.map(r => String(r[c.key] ?? '').length)));
|
|
68
|
+
const header = columns.map((c, i) => c.label.padEnd(widths[i])).join(' ');
|
|
69
|
+
const divider = widths.map(w => '-'.repeat(w)).join(' ');
|
|
70
|
+
console.log(header);
|
|
71
|
+
console.log(divider);
|
|
72
|
+
rows.forEach(row => {
|
|
73
|
+
console.log(columns.map((c, i) => String(row[c.key] ?? '').padEnd(widths[i])).join(' '));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─────────────────────────────────────────
|
|
78
|
+
// CLI
|
|
79
|
+
// ─────────────────────────────────────────
|
|
80
|
+
const program = new Command();
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.name('glidepool')
|
|
84
|
+
.description('CLI for GlidePool — autonomous DLMM agent platform on Base Mainnet')
|
|
85
|
+
.version(pkg.version)
|
|
86
|
+
.option('--api <url>', 'GlidePool API URL (or set GLIDEPOOL_API_URL env var)')
|
|
87
|
+
.option('--json', 'Output raw JSON');
|
|
88
|
+
|
|
89
|
+
// ── config ──────────────────────────────
|
|
90
|
+
const config = program.command('config').description('Manage CLI configuration');
|
|
91
|
+
|
|
92
|
+
config
|
|
93
|
+
.command('set-api <url>')
|
|
94
|
+
.description('Save the GlidePool API URL to ~/.glidepool/config.json')
|
|
95
|
+
.action((url) => {
|
|
96
|
+
const cfg = loadConfig();
|
|
97
|
+
cfg.apiUrl = url.replace(/\/$/, '');
|
|
98
|
+
saveConfig(cfg);
|
|
99
|
+
console.log(`Saved API URL: ${cfg.apiUrl}`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
config
|
|
103
|
+
.command('show')
|
|
104
|
+
.description('Show current configuration')
|
|
105
|
+
.action(() => {
|
|
106
|
+
printJson({ configFile: CONFIG_FILE, ...loadConfig() });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ── pools ───────────────────────────────
|
|
110
|
+
const pools = program.command('pools').description('List and inspect Maverick V2 pools');
|
|
111
|
+
|
|
112
|
+
pools
|
|
113
|
+
.command('list')
|
|
114
|
+
.description('List all supported pools with live TVL, price, and fee rate')
|
|
115
|
+
.action(async (_, cmd) => {
|
|
116
|
+
const opts = program.opts();
|
|
117
|
+
const api = getApiUrl(opts);
|
|
118
|
+
const data = await apiFetch(api, '/api/pools');
|
|
119
|
+
if (opts.json) { printJson(data); return; }
|
|
120
|
+
printTable(data, [
|
|
121
|
+
{ key: 'tokenASymbol', label: 'TOKEN A' },
|
|
122
|
+
{ key: 'tokenBSymbol', label: 'TOKEN B' },
|
|
123
|
+
{ key: 'tvlUsd', label: 'TVL (USD)' },
|
|
124
|
+
{ key: 'currentPrice', label: 'PRICE' },
|
|
125
|
+
{ key: 'feeRate', label: 'FEE' },
|
|
126
|
+
{ key: 'poolAddress', label: 'ADDRESS' },
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
pools
|
|
131
|
+
.command('get <address>')
|
|
132
|
+
.description('Get details for a specific pool')
|
|
133
|
+
.action(async (address) => {
|
|
134
|
+
const opts = program.opts();
|
|
135
|
+
const api = getApiUrl(opts);
|
|
136
|
+
const data = await apiFetch(api, `/api/pools/${address}`);
|
|
137
|
+
printJson(data);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── agent ────────────────────────────────
|
|
141
|
+
const agent = program.command('agent').description('Create and manage autonomous agents');
|
|
142
|
+
|
|
143
|
+
agent
|
|
144
|
+
.command('create')
|
|
145
|
+
.description('Deploy a new autonomous DLMM agent')
|
|
146
|
+
.requiredOption('--wallet <address>', 'Your wallet address (0x...)')
|
|
147
|
+
.requiredOption('--pool <address>', 'Maverick V2 pool address to monitor')
|
|
148
|
+
.option('--strategy <strategy>', 'Strategy: conservative | balanced | aggressive', 'balanced')
|
|
149
|
+
.option('--budget <usdc>', 'Max USDC budget for liquidity operations', '100')
|
|
150
|
+
.option('--interval <seconds>', 'Analysis interval in seconds (min: 30)', '60')
|
|
151
|
+
.action(async (opts) => {
|
|
152
|
+
const api = getApiUrl(program.opts());
|
|
153
|
+
const data = await apiFetch(api, '/api/agents', {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
userAddress: opts.wallet,
|
|
157
|
+
poolAddress: opts.pool,
|
|
158
|
+
strategy: opts.strategy,
|
|
159
|
+
budgetUsdc: Number(opts.budget),
|
|
160
|
+
analysisIntervalSec: Number(opts.interval),
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
if (program.opts().json) { printJson(data); return; }
|
|
164
|
+
console.log(`\nAgent deployed`);
|
|
165
|
+
console.log(` ID: ${data.id}`);
|
|
166
|
+
console.log(` Pool: ${data.poolAddress}`);
|
|
167
|
+
console.log(` Strategy: ${data.strategy}`);
|
|
168
|
+
console.log(` Budget: ${data.budgetUsdc} USDC`);
|
|
169
|
+
console.log(` Status: ${data.status}`);
|
|
170
|
+
console.log(`\nMonitor with: glidepool agent actions ${data.id}`);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
agent
|
|
174
|
+
.command('list')
|
|
175
|
+
.description('List all agents for a wallet address')
|
|
176
|
+
.requiredOption('--wallet <address>', 'Wallet address (0x...)')
|
|
177
|
+
.action(async (opts) => {
|
|
178
|
+
const api = getApiUrl(program.opts());
|
|
179
|
+
const data = await apiFetch(api, `/api/agents?userAddress=${encodeURIComponent(opts.wallet)}`);
|
|
180
|
+
if (program.opts().json) { printJson(data); return; }
|
|
181
|
+
if (!data.length) { console.log('No agents found for this wallet.'); return; }
|
|
182
|
+
printTable(data, [
|
|
183
|
+
{ key: 'id', label: 'AGENT ID' },
|
|
184
|
+
{ key: 'poolAddress', label: 'POOL' },
|
|
185
|
+
{ key: 'strategy', label: 'STRATEGY' },
|
|
186
|
+
{ key: 'budgetUsdc', label: 'BUDGET USDC' },
|
|
187
|
+
{ key: 'status', label: 'STATUS' },
|
|
188
|
+
{ key: 'lastAnalysisAt', label: 'LAST ANALYSIS' },
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
agent
|
|
193
|
+
.command('get <agentId>')
|
|
194
|
+
.description('Get details for a specific agent')
|
|
195
|
+
.action(async (agentId) => {
|
|
196
|
+
const api = getApiUrl(program.opts());
|
|
197
|
+
const data = await apiFetch(api, `/api/agents/${agentId}`);
|
|
198
|
+
printJson(data);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
agent
|
|
202
|
+
.command('pause <agentId>')
|
|
203
|
+
.description('Pause a running agent')
|
|
204
|
+
.action(async (agentId) => {
|
|
205
|
+
const api = getApiUrl(program.opts());
|
|
206
|
+
await apiFetch(api, `/api/agents/${agentId}/status`, {
|
|
207
|
+
method: 'PUT',
|
|
208
|
+
body: JSON.stringify({ status: 'paused' }),
|
|
209
|
+
});
|
|
210
|
+
console.log(`Agent ${agentId} paused.`);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
agent
|
|
214
|
+
.command('resume <agentId>')
|
|
215
|
+
.description('Resume a paused agent')
|
|
216
|
+
.action(async (agentId) => {
|
|
217
|
+
const api = getApiUrl(program.opts());
|
|
218
|
+
await apiFetch(api, `/api/agents/${agentId}/status`, {
|
|
219
|
+
method: 'PUT',
|
|
220
|
+
body: JSON.stringify({ status: 'active' }),
|
|
221
|
+
});
|
|
222
|
+
console.log(`Agent ${agentId} resumed.`);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
agent
|
|
226
|
+
.command('stop <agentId>')
|
|
227
|
+
.description('Stop an agent permanently')
|
|
228
|
+
.action(async (agentId) => {
|
|
229
|
+
const api = getApiUrl(program.opts());
|
|
230
|
+
await apiFetch(api, `/api/agents/${agentId}/status`, {
|
|
231
|
+
method: 'PUT',
|
|
232
|
+
body: JSON.stringify({ status: 'stopped' }),
|
|
233
|
+
});
|
|
234
|
+
console.log(`Agent ${agentId} stopped.`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
agent
|
|
238
|
+
.command('actions <agentId>')
|
|
239
|
+
.description('Show LLM decisions (actions) for an agent')
|
|
240
|
+
.option('--limit <n>', 'Number of actions to show', '10')
|
|
241
|
+
.action(async (agentId, opts) => {
|
|
242
|
+
const api = getApiUrl(program.opts());
|
|
243
|
+
const data = await apiFetch(api, `/api/agents/${agentId}/actions?limit=${opts.limit}`);
|
|
244
|
+
if (program.opts().json) { printJson(data); return; }
|
|
245
|
+
if (!data.length) { console.log('No actions yet. Agent will analyze on its next cycle.'); return; }
|
|
246
|
+
data.forEach((a) => {
|
|
247
|
+
const ts = new Date(a.createdAt).toLocaleTimeString();
|
|
248
|
+
const rec = a.llmRecommendation;
|
|
249
|
+
console.log(`\n[${ts}] ${a.actionType.toUpperCase()} — ${a.status}`);
|
|
250
|
+
if (rec?.riskLevel) console.log(` Risk: ${rec.riskLevel}`);
|
|
251
|
+
if (a.llmReasoning) console.log(` Reason: ${a.llmReasoning.slice(0, 200)}...`);
|
|
252
|
+
if (rec?.recommendation?.suggestedBinRange) {
|
|
253
|
+
const r = rec.recommendation.suggestedBinRange;
|
|
254
|
+
console.log(` Bins: lower=${r.lowerTick} upper=${r.upperTick}`);
|
|
255
|
+
}
|
|
256
|
+
if (a.txHash) console.log(` TX: ${a.txHash}`);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ── positions ────────────────────────────
|
|
261
|
+
program
|
|
262
|
+
.command('positions <walletAddress>')
|
|
263
|
+
.description('List Maverick V2 LP positions for a wallet on Base Mainnet')
|
|
264
|
+
.action(async (walletAddress) => {
|
|
265
|
+
const api = getApiUrl(program.opts());
|
|
266
|
+
const data = await apiFetch(api, `/api/positions/${walletAddress}`);
|
|
267
|
+
if (program.opts().json) { printJson(data); return; }
|
|
268
|
+
if (!data.length) { console.log('No Maverick V2 positions found for this wallet.'); return; }
|
|
269
|
+
printTable(data, [
|
|
270
|
+
{ key: 'nftId', label: 'NFT ID' },
|
|
271
|
+
{ key: 'tokenASymbol', label: 'TOKEN A' },
|
|
272
|
+
{ key: 'tokenBSymbol', label: 'TOKEN B' },
|
|
273
|
+
{ key: 'valueUsd', label: 'VALUE USD' },
|
|
274
|
+
{ key: 'amountA', label: 'AMOUNT A' },
|
|
275
|
+
{ key: 'amountB', label: 'AMOUNT B' },
|
|
276
|
+
{ key: 'binCount', label: 'BINS' },
|
|
277
|
+
]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ── advisor ──────────────────────────────
|
|
281
|
+
program
|
|
282
|
+
.command('advisor')
|
|
283
|
+
.description('Get a Claude Opus 4 AI recommendation for a pool position')
|
|
284
|
+
.requiredOption('--pool <address>', 'Pool address to analyze')
|
|
285
|
+
.requiredOption('--goal <text>', 'Your liquidity goal (e.g. "maximize fee income with low IL")')
|
|
286
|
+
.option('--nft <id>', 'NFT position ID if analyzing an existing position')
|
|
287
|
+
.option('--payment-proof <base64>', 'x402 payment proof header (if server requires payment)')
|
|
288
|
+
.action(async (opts) => {
|
|
289
|
+
const api = getApiUrl(program.opts());
|
|
290
|
+
const qs = new URLSearchParams({ poolAddress: opts.pool, userGoal: opts.goal });
|
|
291
|
+
if (opts.nft) qs.set('nftId', opts.nft);
|
|
292
|
+
const headers = {};
|
|
293
|
+
if (opts.paymentProof) headers['x-payment-proof'] = opts.paymentProof;
|
|
294
|
+
const data = await apiFetch(api, `/api/advisor?${qs}`, { headers });
|
|
295
|
+
if (program.opts().json) { printJson(data); return; }
|
|
296
|
+
console.log(`\n── AI Advisor ──────────────────────────`);
|
|
297
|
+
console.log(` Action: ${data.recommendation?.action?.toUpperCase()}`);
|
|
298
|
+
console.log(` Risk: ${data.riskLevel}`);
|
|
299
|
+
console.log(` Summary: ${data.summary}`);
|
|
300
|
+
if (data.recommendation?.suggestedBinRange) {
|
|
301
|
+
const r = data.recommendation.suggestedBinRange;
|
|
302
|
+
console.log(` Bins: lower=${r.lowerTick} upper=${r.upperTick}`);
|
|
303
|
+
}
|
|
304
|
+
if (data.recommendation?.suggestedWithdrawPercent) {
|
|
305
|
+
console.log(` Withdraw: ${data.recommendation.suggestedWithdrawPercent}%`);
|
|
306
|
+
}
|
|
307
|
+
console.log(`\n Reasoning:`);
|
|
308
|
+
console.log(` ${data.recommendation?.reasoning}`);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glide-pool/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI for managing GlidePool autonomous DLMM agents on Base Mainnet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"glidepool": "./bin/glidepool
|
|
7
|
+
"glidepool": "./bin/glidepool"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
-
"src",
|
|
12
11
|
"README.md"
|
|
13
12
|
],
|
|
14
13
|
"keywords": [
|