crypull 1.0.1 → 1.0.2

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/src/cli.ts CHANGED
@@ -1,327 +1,440 @@
1
- #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import pc from 'picocolors';
4
- import * as asciichart from 'asciichart';
5
- import { crypull } from './index.js';
6
-
7
- const program = new Command();
8
-
9
- program
10
- .name('crypull')
11
- .description('A human-friendly CLI to fetch crypto prices and info from multiple providers')
12
- .version('1.0.0');
13
-
14
- program
15
- .command('price <query>')
16
- .description('Get the current price of a cryptocurrency (symbol or contract address)')
17
- .action(async (query: string) => {
18
- console.log(pc.cyan(`\nFetching price for ${pc.bold(query)}...`));
19
-
20
- const data = await crypull.price(query);
21
-
22
- if (!data) {
23
- console.log(pc.red(`\n✖ Could not find price for "${query}".`));
24
- return;
25
- }
26
-
27
- console.log(pc.green(`\n✔ Success! Found via ${pc.bold(data.source)}`));
28
- console.log(pc.white(`----------------------------------------`));
29
- console.log(`${pc.gray('Symbol:')} ${pc.yellow(pc.bold(data.symbol))}`);
30
- console.log(`${pc.gray('Price:')} ${pc.green('$' + data.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 }))}`);
31
- console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
32
- console.log(pc.white(`----------------------------------------\n`));
33
- });
34
-
35
- program
36
- .command('info <query>')
37
- .description('Get detailed info about a cryptocurrency (market cap, volume, FDV, etc.)')
38
- .action(async (query: string) => {
39
- console.log(pc.cyan(`\nFetching detailed info for ${pc.bold(query)}...`));
40
-
41
- const data = await crypull.info(query);
42
-
43
- if (!data) {
44
- console.log(pc.red(`\n✖ Could not find info for "${query}".`));
45
- return;
46
- }
47
-
48
- console.log(pc.green(`\n✔ Success! Found via ${pc.bold(data.source)}`));
49
- console.log(pc.white(`----------------------------------------`));
50
- console.log(`${pc.gray('Name:')} ${pc.white(pc.bold(data.name))}`);
51
- console.log(`${pc.gray('Symbol:')} ${pc.yellow(pc.bold(data.symbol))}`);
52
- console.log(`${pc.gray('Price:')} ${pc.green('$' + data.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 }))}`);
53
-
54
- if (data.marketCap) {
55
- console.log(`${pc.gray('MarketCap:')} ${pc.white('$' + data.marketCap.toLocaleString())}`);
56
- }
57
- if (data.marketCapRank) {
58
- console.log(`${pc.gray('Rank:')} ${pc.magenta('#' + data.marketCapRank)}`);
59
- }
60
- if (data.fdv) {
61
- console.log(`${pc.gray('FDV:')} ${pc.white('$' + data.fdv.toLocaleString())}`);
62
- }
63
- if (data.circulatingSupply) {
64
- console.log(`${pc.gray('Circ. Sup:')} ${pc.white(data.circulatingSupply.toLocaleString())}`);
65
- }
66
- if (data.totalSupply) {
67
- console.log(`${pc.gray('Total Sup:')} ${pc.white(data.totalSupply.toLocaleString())}`);
68
- }
69
- if (data.maxSupply) {
70
- console.log(`${pc.gray('Max Sup:')} ${pc.white(data.maxSupply.toLocaleString())}`);
71
- }
72
- if (data.volume24h) {
73
- console.log(`${pc.gray('24h Vol:')} ${pc.white('$' + data.volume24h.toLocaleString())}`);
74
- }
75
- if (data.priceChangePercentage24h !== undefined) {
76
- const pc24 = data.priceChangePercentage24h;
77
- const color = pc24 >= 0 ? pc.green : pc.red;
78
- console.log(`${pc.gray('24h %:')} ${color(pc24.toFixed(2) + '%')}`);
79
- }
80
- if (data.liquidityUsd) {
81
- console.log(`${pc.gray('Liquidity:')} ${pc.white('$' + data.liquidityUsd.toLocaleString())}`);
82
- }
83
- if (data.ath) {
84
- console.log(`${pc.gray('ATH:')} ${pc.green('$' + data.ath.toLocaleString())} ${data.athDate ? pc.gray(`(${new Date(data.athDate).toLocaleDateString()})`) : ''}`);
85
- }
86
- if (data.atl) {
87
- console.log(`${pc.gray('ATL:')} ${pc.red('$' + data.atl.toLocaleString())} ${data.atlDate ? pc.gray(`(${new Date(data.atlDate).toLocaleDateString()})`) : ''}`);
88
- }
89
- if (data.network) {
90
- console.log(`${pc.gray('Network:')} ${pc.magenta(data.network)}`);
91
- }
92
- if (data.address) {
93
- console.log(`${pc.gray('Address:')} ${pc.gray(data.address)}`);
94
- }
95
-
96
- if (data.links && Object.values(data.links).some(l => !!l)) {
97
- console.log(pc.white(`\n--- Links ---`));
98
- if (data.links.website) console.log(`${pc.gray('Website:')} ${pc.blue(data.links.website)}`);
99
- if (data.links.twitter) console.log(`${pc.gray('Twitter:')} ${pc.blue(data.links.twitter)}`);
100
- if (data.links.telegram) console.log(`${pc.gray('Telegram:')} ${pc.blue(data.links.telegram)}`);
101
- if (data.links.discord) console.log(`${pc.gray('Discord:')} ${pc.blue(data.links.discord)}`);
102
- if (data.links.github) console.log(`${pc.gray('GitHub:')} ${pc.blue(data.links.github)}`);
103
- }
104
-
105
- if (data.description && data.description.length > 0) {
106
- console.log(pc.white(`\n--- Description ---`));
107
- // Truncate to 300 chars to not flood terminal
108
- const desc = data.description.replace(/(<([^>]+)>)/gi, ""); // strip html
109
- const truncated = desc.length > 300 ? desc.substring(0, 300) + '...' : desc;
110
- console.log(pc.gray(truncated));
111
- }
112
-
113
- if (data.pairs && data.pairs.length > 0) {
114
- console.log(pc.white(`\n--- Top Trading Pairs ---`));
115
- data.pairs.slice(0, 5).forEach((p, i) => {
116
- const title = `${p.baseTokenSymbol}/${p.quoteTokenSymbol}`;
117
- const priceStr = p.priceUsd ? `$${p.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
118
- const volStr = p.volume24h ? `$${p.volume24h.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : 'N/A';
119
- console.log(`${pc.blue(String(i + 1) + '.')} ${pc.yellow(title.padEnd(12, ' '))} | ${pc.green(priceStr.padEnd(12, ' '))} | Vol: ${pc.white(volStr)} | DEX: ${pc.magenta(p.dexId)}`);
120
- });
121
- }
122
-
123
- console.log(pc.white(`\n----------------------------------------`));
124
- console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
125
- console.log(pc.white(`----------------------------------------\n`));
126
- });
127
-
128
- program
129
- .command('top')
130
- .description('Show top 50 cryptocurrencies by market cap')
131
- .action(async () => {
132
- console.log(pc.cyan(`\nFetching top 50 cryptocurrencies...`));
133
-
134
- const results = await crypull.top();
135
-
136
- if (!results || results.length === 0) {
137
- console.log(pc.red(`\n✖ Could not fetch top coins at this time.`));
138
- return;
139
- }
140
-
141
- console.log(pc.green(`\n✔ Top 50 Cryptocurrencies:`));
142
- console.log(pc.white(`----------------------------------------`));
143
-
144
- results.forEach((res, i) => {
145
- const priceStr = res.priceUsd ? `$${res.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
146
- const mcapStr = res.marketCap ? `$${res.marketCap.toLocaleString()}` : 'N/A';
147
-
148
- let changeStr = '';
149
- if (res.priceChange24h !== undefined && res.priceChange24h !== null) {
150
- const color = res.priceChange24h >= 0 ? pc.green : pc.red;
151
- changeStr = color(`(${res.priceChange24h >= 0 ? '+' : ''}${res.priceChange24h.toFixed(2)}%)`);
152
- }
153
-
154
- console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.green(priceStr.padEnd(12, ' '))} ${changeStr.padEnd(15, ' ')} | Mcap: ${pc.white(mcapStr)}`);
155
- });
156
-
157
- console.log(pc.white(`----------------------------------------\n`));
158
- });
159
-
160
- program
161
- .command('search <query>')
162
- .description('Search for tokens or coins across multiple providers')
163
- .action(async (query: string) => {
164
- console.log(pc.cyan(`\nSearching for ${pc.bold(query)}...`));
165
-
166
- const results = await crypull.search(query);
167
-
168
- if (!results || results.length === 0) {
169
- console.log(pc.red(`\n✖ No results found for "${query}".`));
170
- return;
171
- }
172
-
173
- console.log(pc.green(`\n✔ Found ${results.length} results:`));
174
- console.log(pc.white(`----------------------------------------`));
175
-
176
- results.forEach((res, i) => {
177
- console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.white(res.name)} ${pc.gray(`(via ${res.source})`)}`);
178
- if (res.network) {
179
- console.log(` ${pc.gray('Network:')} ${pc.magenta(res.network)}`);
180
- }
181
- if (res.id && res.id.length > 20) {
182
- console.log(` ${pc.gray('Address:')} ${pc.gray(res.id)}`);
183
- }
184
- });
185
-
186
- console.log(pc.white(`----------------------------------------\n`));
187
- });
188
-
189
- program
190
- .command('trending')
191
- .description('Show top 10 trending coins/tokens right now')
192
- .action(async () => {
193
- console.log(pc.cyan(`\nFetching top trending coins...`));
194
-
195
- const results = await crypull.trending();
196
-
197
- if (!results || results.length === 0) {
198
- console.log(pc.red(`\n✖ Could not fetch trending data at this time.`));
199
- return;
200
- }
201
-
202
- console.log(pc.green(`\n✔ Top 10 Trending Coins:`));
203
- console.log(pc.white(`----------------------------------------`));
204
-
205
- results.forEach((res, i) => {
206
- const priceStr = res.priceUsd ? `$${res.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
207
- let changeStr = '';
208
- if (res.priceChange24h !== undefined) {
209
- const color = res.priceChange24h >= 0 ? pc.green : pc.red;
210
- changeStr = color(`(${res.priceChange24h >= 0 ? '+' : ''}${res.priceChange24h.toFixed(2)}%)`);
211
- }
212
- const rankStr = res.marketCapRank ? pc.magenta(`#${res.marketCapRank}`) : '';
213
-
214
- console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.green(priceStr.padEnd(12, ' '))} ${changeStr.padEnd(15, ' ')} | ${rankStr}`);
215
- });
216
-
217
- console.log(pc.white(`----------------------------------------\n`));
218
- });
219
-
220
- program
221
- .command('market')
222
- .description('Show global cryptocurrency market overview')
223
- .action(async () => {
224
- console.log(pc.cyan(`\nFetching global market data...`));
225
-
226
- const data = await crypull.market();
227
-
228
- if (!data) {
229
- console.log(pc.red(`\n✖ Could not fetch global market data at this time.`));
230
- return;
231
- }
232
-
233
- console.log(pc.green(`\n✔ Global Market Overview:`));
234
- console.log(pc.white(`----------------------------------------`));
235
- console.log(`${pc.gray('Total Market Cap:')} ${pc.white('$' + data.totalMarketCapUsd.toLocaleString())}`);
236
- console.log(`${pc.gray('24h Volume:')} ${pc.white('$' + data.totalVolume24hUsd.toLocaleString())}`);
237
- console.log(`${pc.gray('BTC Dominance:')} ${pc.yellow(data.bitcoinDominancePercentage.toFixed(2) + '%')}`);
238
- console.log(`${pc.gray('ETH Dominance:')} ${pc.blue(data.ethereumDominancePercentage.toFixed(2) + '%')}`);
239
- console.log(`${pc.gray('Active Coins:')} ${pc.white(data.activeCryptocurrencies.toLocaleString())}`);
240
- console.log(pc.white(`----------------------------------------`));
241
- console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
242
- console.log(pc.white(`----------------------------------------\n`));
243
- });
244
-
245
- program
246
- .command('sentiment')
247
- .description('Show current Crypto Fear & Greed Index')
248
- .action(async () => {
249
- console.log(pc.cyan(`\nFetching Fear & Greed Index...`));
250
-
251
- const data = await crypull.sentiment();
252
-
253
- if (!data) {
254
- console.log(pc.red(`\n✖ Could not fetch sentiment data at this time.`));
255
- return;
256
- }
257
-
258
- const value = data.value;
259
- let color = pc.white;
260
- if (value <= 25) color = pc.red;
261
- else if (value <= 45) color = pc.yellow;
262
- else if (value <= 55) color = pc.white;
263
- else if (value <= 75) color = pc.green;
264
- else color = pc.blue;
265
-
266
- console.log(pc.green(`\n✔ Current Market Sentiment:`));
267
- console.log(pc.white(`----------------------------------------`));
268
- console.log(`${pc.gray('Score:')} ${color(pc.bold(value.toString()) + ' / 100')}`);
269
- console.log(`${pc.gray('Classification:')} ${color(pc.bold(data.classification))}`);
270
- console.log(pc.white(`----------------------------------------\n`));
271
- });
272
-
273
- program
274
- .command('gas')
275
- .description('Show current Ethereum network gas prices')
276
- .action(async () => {
277
- console.log(pc.cyan(`\nFetching network gas prices...`));
278
-
279
- const data = await crypull.gas();
280
-
281
- if (!data) {
282
- console.log(pc.red(`\n✖ Could not fetch gas prices at this time.`));
283
- return;
284
- }
285
-
286
- console.log(pc.green(`\n✔ ${data.network} Gas Tracker (Gwei):`));
287
- console.log(pc.white(`----------------------------------------`));
288
- console.log(`${pc.gray('Low (Safe):')} ${pc.green(data.low + ' gwei')}`);
289
- console.log(`${pc.gray('Average:')} ${pc.yellow(data.average + ' gwei')}`);
290
- console.log(`${pc.gray('High (Fast):')} ${pc.red(data.high + ' gwei')}`);
291
- if (data.baseFee) {
292
- console.log(`${pc.gray('Base Fee:')} ${pc.white(data.baseFee + ' gwei')}`);
293
- }
294
- console.log(pc.white(`----------------------------------------\n`));
295
- });
296
-
297
- program
298
- .command('chart <query>')
299
- .description('Draw a terminal ASCII chart of historical prices')
300
- .option('-d, --days <days>', 'Number of days to chart', '7')
301
- .action(async (query: string, options) => {
302
- const days = parseInt(options.days) || 7;
303
- console.log(pc.cyan(`\nFetching ${days}-day chart data for ${pc.bold(query)}...`));
304
-
305
- const data = await crypull.chart(query, days);
306
-
307
- if (!data || !data.prices || data.prices.length === 0) {
308
- console.log(pc.red(`\n✖ Could not fetch chart data for "${query}". Note: Charts may only be available for major coins.`));
309
- return;
310
- }
311
-
312
- // Determine color based on price action
313
- const firstPrice = data.prices[0];
314
- const lastPrice = data.prices[data.prices.length - 1];
315
- const isUp = lastPrice >= firstPrice;
316
-
317
- const chartConfig = {
318
- colors: [isUp ? asciichart.lightgreen : asciichart.lightred],
319
- height: 10
320
- };
321
-
322
- console.log(pc.green(`\n✔ ${days}-Day Chart for ${query.toUpperCase()}:`));
323
- console.log(pc.gray(`Min: $${data.minPrice.toLocaleString(undefined, { maximumFractionDigits: 6 })} | Max: $${data.maxPrice.toLocaleString(undefined, { maximumFractionDigits: 6 })}`));
324
- console.log(pc.white(`\n` + asciichart.plot(data.prices, chartConfig) + `\n`));
325
- });
326
-
327
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import pc from 'picocolors';
4
+ import * as asciichart from 'asciichart';
5
+ import { crypull } from './index.js';
6
+ import { intro, outro, select, text, isCancel } from '@clack/prompts';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('crypull')
12
+ .description('A human-friendly CLI to fetch crypto prices and info from multiple providers')
13
+ .version('1.0.1');
14
+
15
+ const handlePrice = async (query: string) => {
16
+ console.log(pc.cyan(`\nFetching price for ${pc.bold(query)}...`));
17
+
18
+ const data = await crypull.price(query);
19
+
20
+ if (!data) {
21
+ console.log(pc.red(`\n✖ Could not find price for "${query}".`));
22
+ return;
23
+ }
24
+
25
+ console.log(pc.green(`\n✔ Success! Found via ${pc.bold(data.source)}`));
26
+ console.log(pc.white(`----------------------------------------`));
27
+ console.log(`${pc.gray('Symbol:')} ${pc.yellow(pc.bold(data.symbol))}`);
28
+ console.log(`${pc.gray('Price:')} ${pc.green('$' + data.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 }))}`);
29
+ console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
30
+ console.log(pc.white(`----------------------------------------\n`));
31
+ };
32
+
33
+ const handleInfo = async (query: string) => {
34
+ console.log(pc.cyan(`\nFetching detailed info for ${pc.bold(query)}...`));
35
+
36
+ const data = await crypull.info(query);
37
+
38
+ if (!data) {
39
+ console.log(pc.red(`\n✖ Could not find info for "${query}".`));
40
+ return;
41
+ }
42
+
43
+ console.log(pc.green(`\n✔ Success! Found via ${pc.bold(data.source)}`));
44
+ console.log(pc.white(`----------------------------------------`));
45
+ console.log(`${pc.gray('Name:')} ${pc.white(pc.bold(data.name))}`);
46
+ console.log(`${pc.gray('Symbol:')} ${pc.yellow(pc.bold(data.symbol))}`);
47
+ console.log(`${pc.gray('Price:')} ${pc.green('$' + data.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 }))}`);
48
+
49
+ if (data.marketCap) {
50
+ console.log(`${pc.gray('MarketCap:')} ${pc.white('$' + data.marketCap.toLocaleString())}`);
51
+ }
52
+ if (data.marketCapRank) {
53
+ console.log(`${pc.gray('Rank:')} ${pc.magenta('#' + data.marketCapRank)}`);
54
+ }
55
+ if (data.fdv) {
56
+ console.log(`${pc.gray('FDV:')} ${pc.white('$' + data.fdv.toLocaleString())}`);
57
+ }
58
+ if (data.circulatingSupply) {
59
+ console.log(`${pc.gray('Circ. Sup:')} ${pc.white(data.circulatingSupply.toLocaleString())}`);
60
+ }
61
+ if (data.totalSupply) {
62
+ console.log(`${pc.gray('Total Sup:')} ${pc.white(data.totalSupply.toLocaleString())}`);
63
+ }
64
+ if (data.maxSupply) {
65
+ console.log(`${pc.gray('Max Sup:')} ${pc.white(data.maxSupply.toLocaleString())}`);
66
+ }
67
+ if (data.volume24h) {
68
+ console.log(`${pc.gray('24h Vol:')} ${pc.white('$' + data.volume24h.toLocaleString())}`);
69
+ }
70
+ if (data.priceChangePercentage24h !== undefined) {
71
+ const pc24 = data.priceChangePercentage24h;
72
+ const color = pc24 >= 0 ? pc.green : pc.red;
73
+ console.log(`${pc.gray('24h %:')} ${color(pc24.toFixed(2) + '%')}`);
74
+ }
75
+ if (data.liquidityUsd) {
76
+ console.log(`${pc.gray('Liquidity:')} ${pc.white('$' + data.liquidityUsd.toLocaleString())}`);
77
+ }
78
+ if (data.ath) {
79
+ console.log(`${pc.gray('ATH:')} ${pc.green('$' + data.ath.toLocaleString())} ${data.athDate ? pc.gray(`(${new Date(data.athDate).toLocaleDateString()})`) : ''}`);
80
+ }
81
+ if (data.atl) {
82
+ console.log(`${pc.gray('ATL:')} ${pc.red('$' + data.atl.toLocaleString())} ${data.atlDate ? pc.gray(`(${new Date(data.atlDate).toLocaleDateString()})`) : ''}`);
83
+ }
84
+ if (data.network) {
85
+ console.log(`${pc.gray('Network:')} ${pc.magenta(data.network)}`);
86
+ }
87
+ if (data.address) {
88
+ console.log(`${pc.gray('Address:')} ${pc.gray(data.address)}`);
89
+ }
90
+
91
+ if (data.links && Object.values(data.links).some(l => !!l)) {
92
+ console.log(pc.white(`\n--- Links ---`));
93
+ if (data.links.website) console.log(`${pc.gray('Website:')} ${pc.blue(data.links.website)}`);
94
+ if (data.links.twitter) console.log(`${pc.gray('Twitter:')} ${pc.blue(data.links.twitter)}`);
95
+ if (data.links.telegram) console.log(`${pc.gray('Telegram:')} ${pc.blue(data.links.telegram)}`);
96
+ if (data.links.discord) console.log(`${pc.gray('Discord:')} ${pc.blue(data.links.discord)}`);
97
+ if (data.links.github) console.log(`${pc.gray('GitHub:')} ${pc.blue(data.links.github)}`);
98
+ }
99
+
100
+ if (data.description && data.description.length > 0) {
101
+ console.log(pc.white(`\n--- Description ---`));
102
+ // Truncate to 300 chars to not flood terminal
103
+ const desc = data.description.replace(/(<([^>]+)>)/gi, ""); // strip html
104
+ const truncated = desc.length > 300 ? desc.substring(0, 300) + '...' : desc;
105
+ console.log(pc.gray(truncated));
106
+ }
107
+
108
+ if (data.pairs && data.pairs.length > 0) {
109
+ console.log(pc.white(`\n--- Top Trading Pairs ---`));
110
+ data.pairs.slice(0, 5).forEach((p, i) => {
111
+ const title = `${p.baseTokenSymbol}/${p.quoteTokenSymbol}`;
112
+ const priceStr = p.priceUsd ? `$${p.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
113
+ const volStr = p.volume24h ? `$${p.volume24h.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : 'N/A';
114
+ console.log(`${pc.blue(String(i + 1) + '.')} ${pc.yellow(title.padEnd(12, ' '))} | ${pc.green(priceStr.padEnd(12, ' '))} | Vol: ${pc.white(volStr)} | DEX: ${pc.magenta(p.dexId)}`);
115
+ });
116
+ }
117
+
118
+ console.log(pc.white(`\n----------------------------------------`));
119
+ console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
120
+ console.log(pc.white(`----------------------------------------\n`));
121
+ };
122
+
123
+ const handleTop = async () => {
124
+ console.log(pc.cyan(`\nFetching top 50 cryptocurrencies...`));
125
+
126
+ const results = await crypull.top();
127
+
128
+ if (!results || results.length === 0) {
129
+ console.log(pc.red(`\n✖ Could not fetch top coins at this time.`));
130
+ return;
131
+ }
132
+
133
+ console.log(pc.green(`\n✔ Top 50 Cryptocurrencies:`));
134
+ console.log(pc.white(`----------------------------------------`));
135
+
136
+ results.forEach((res, i) => {
137
+ const priceStr = res.priceUsd ? `$${res.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
138
+ const mcapStr = res.marketCap ? `$${res.marketCap.toLocaleString()}` : 'N/A';
139
+
140
+ let changeStr = '';
141
+ if (res.priceChange24h !== undefined && res.priceChange24h !== null) {
142
+ const color = res.priceChange24h >= 0 ? pc.green : pc.red;
143
+ changeStr = color(`(${res.priceChange24h >= 0 ? '+' : ''}${res.priceChange24h.toFixed(2)}%)`);
144
+ }
145
+
146
+ console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.green(priceStr.padEnd(12, ' '))} ${changeStr.padEnd(15, ' ')} | Mcap: ${pc.white(mcapStr)}`);
147
+ });
148
+
149
+ console.log(pc.white(`----------------------------------------\n`));
150
+ };
151
+
152
+ const handleSearch = async (query: string) => {
153
+ console.log(pc.cyan(`\nSearching for ${pc.bold(query)}...`));
154
+
155
+ const results = await crypull.search(query);
156
+
157
+ if (!results || results.length === 0) {
158
+ console.log(pc.red(`\n✖ No results found for "${query}".`));
159
+ return;
160
+ }
161
+
162
+ console.log(pc.green(`\n✔ Found ${results.length} results:`));
163
+ console.log(pc.white(`----------------------------------------`));
164
+
165
+ results.forEach((res, i) => {
166
+ console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.white(res.name)} ${pc.gray(`(via ${res.source})`)}`);
167
+ if (res.network) {
168
+ console.log(` ${pc.gray('Network:')} ${pc.magenta(res.network)}`);
169
+ }
170
+ if (res.id && res.id.length > 20) {
171
+ console.log(` ${pc.gray('Address:')} ${pc.gray(res.id)}`);
172
+ }
173
+ });
174
+
175
+ console.log(pc.white(`----------------------------------------\n`));
176
+ };
177
+
178
+ const handleTrending = async () => {
179
+ console.log(pc.cyan(`\nFetching top trending coins...`));
180
+
181
+ const results = await crypull.trending();
182
+
183
+ if (!results || results.length === 0) {
184
+ console.log(pc.red(`\n✖ Could not fetch trending data at this time.`));
185
+ return;
186
+ }
187
+
188
+ console.log(pc.green(`\n✔ Top 10 Trending Coins:`));
189
+ console.log(pc.white(`----------------------------------------`));
190
+
191
+ results.forEach((res, i) => {
192
+ const priceStr = res.priceUsd ? `$${res.priceUsd.toLocaleString(undefined, { maximumFractionDigits: 6 })}` : 'N/A';
193
+ let changeStr = '';
194
+ if (res.priceChange24h !== undefined) {
195
+ const color = res.priceChange24h >= 0 ? pc.green : pc.red;
196
+ changeStr = color(`(${res.priceChange24h >= 0 ? '+' : ''}${res.priceChange24h.toFixed(2)}%)`);
197
+ }
198
+ const rankStr = res.marketCapRank ? pc.magenta(`#${res.marketCapRank}`) : '';
199
+
200
+ console.log(`${pc.blue(String(i + 1).padStart(2, ' '))}. ${pc.yellow(pc.bold(res.symbol.padEnd(8, ' ')))} | ${pc.green(priceStr.padEnd(12, ' '))} ${changeStr.padEnd(15, ' ')} | ${rankStr}`);
201
+ });
202
+
203
+ console.log(pc.white(`----------------------------------------\n`));
204
+ };
205
+
206
+ const handleMarket = async () => {
207
+ console.log(pc.cyan(`\nFetching global market data...`));
208
+
209
+ const data = await crypull.market();
210
+
211
+ if (!data) {
212
+ console.log(pc.red(`\n✖ Could not fetch global market data at this time.`));
213
+ return;
214
+ }
215
+
216
+ console.log(pc.green(`\n✔ Global Market Overview:`));
217
+ console.log(pc.white(`----------------------------------------`));
218
+ console.log(`${pc.gray('Total Market Cap:')} ${pc.white('$' + data.totalMarketCapUsd.toLocaleString())}`);
219
+ console.log(`${pc.gray('24h Volume:')} ${pc.white('$' + data.totalVolume24hUsd.toLocaleString())}`);
220
+ console.log(`${pc.gray('BTC Dominance:')} ${pc.yellow(data.bitcoinDominancePercentage.toFixed(2) + '%')}`);
221
+ console.log(`${pc.gray('ETH Dominance:')} ${pc.blue(data.ethereumDominancePercentage.toFixed(2) + '%')}`);
222
+ console.log(`${pc.gray('Active Coins:')} ${pc.white(data.activeCryptocurrencies.toLocaleString())}`);
223
+ console.log(pc.white(`----------------------------------------`));
224
+ console.log(`${pc.gray('Updated:')} ${new Date(data.lastUpdated).toLocaleTimeString()}`);
225
+ console.log(pc.white(`----------------------------------------\n`));
226
+ };
227
+
228
+ const handleSentiment = async () => {
229
+ console.log(pc.cyan(`\nFetching Fear & Greed Index...`));
230
+
231
+ const data = await crypull.sentiment();
232
+
233
+ if (!data) {
234
+ console.log(pc.red(`\n✖ Could not fetch sentiment data at this time.`));
235
+ return;
236
+ }
237
+
238
+ const value = data.value;
239
+ let color = pc.white;
240
+ if (value <= 25) color = pc.red;
241
+ else if (value <= 45) color = pc.yellow;
242
+ else if (value <= 55) color = pc.white;
243
+ else if (value <= 75) color = pc.green;
244
+ else color = pc.blue;
245
+
246
+ console.log(pc.green(`\n✔ Current Market Sentiment:`));
247
+ console.log(pc.white(`----------------------------------------`));
248
+ console.log(`${pc.gray('Score:')} ${color(pc.bold(value.toString()) + ' / 100')}`);
249
+ console.log(`${pc.gray('Classification:')} ${color(pc.bold(data.classification))}`);
250
+ console.log(pc.white(`----------------------------------------\n`));
251
+ };
252
+
253
+ const handleGas = async () => {
254
+ console.log(pc.cyan(`\nFetching network gas prices...`));
255
+
256
+ const data = await crypull.gas();
257
+
258
+ if (!data) {
259
+ console.log(pc.red(`\n✖ Could not fetch gas prices at this time.`));
260
+ return;
261
+ }
262
+
263
+ console.log(pc.green(`\n✔ ${data.network} Gas Tracker (Gwei):`));
264
+ console.log(pc.white(`----------------------------------------`));
265
+ console.log(`${pc.gray('Low (Safe):')} ${pc.green(data.low + ' gwei')}`);
266
+ console.log(`${pc.gray('Average:')} ${pc.yellow(data.average + ' gwei')}`);
267
+ console.log(`${pc.gray('High (Fast):')} ${pc.red(data.high + ' gwei')}`);
268
+ if (data.baseFee) {
269
+ console.log(`${pc.gray('Base Fee:')} ${pc.white(data.baseFee + ' gwei')}`);
270
+ }
271
+ console.log(pc.white(`----------------------------------------\n`));
272
+ };
273
+
274
+ const handleChart = async (query: string, options: { days: string }) => {
275
+ const days = parseInt(options.days) || 7;
276
+ console.log(pc.cyan(`\nFetching ${days}-day chart data for ${pc.bold(query)}...`));
277
+
278
+ const data = await crypull.chart(query, days);
279
+
280
+ if (!data || !data.prices || data.prices.length === 0) {
281
+ console.log(pc.red(`\n✖ Could not fetch chart data for "${query}". Note: Charts may only be available for major coins.`));
282
+ return;
283
+ }
284
+
285
+ // Determine color based on price action
286
+ const firstPrice = data.prices[0];
287
+ const lastPrice = data.prices[data.prices.length - 1];
288
+ const isUp = lastPrice >= firstPrice;
289
+
290
+ const chartConfig = {
291
+ colors: [isUp ? asciichart.lightgreen : asciichart.lightred],
292
+ height: 10
293
+ };
294
+
295
+ console.log(pc.green(`\n✔ ${days}-Day Chart for ${query.toUpperCase()}:`));
296
+ console.log(pc.gray(`Min: $${data.minPrice.toLocaleString(undefined, { maximumFractionDigits: 6 })} | Max: $${data.maxPrice.toLocaleString(undefined, { maximumFractionDigits: 6 })}`));
297
+ console.log(pc.white(`\n` + asciichart.plot(data.prices, chartConfig) + `\n`));
298
+ };
299
+
300
+ const handleDonate = () => {
301
+ console.log(pc.cyan(`\n💖 Support the development of crypull:`));
302
+ console.log(pc.white(`----------------------------------------`));
303
+ console.log(`${pc.yellow('BTC:')} ${pc.white('bc1qj4zt3lva4r68xspayg0p8s4hyg40nhjz8fuhqq')}`);
304
+ console.log(`${pc.blue('EVM (All):')} ${pc.white('0x63a875d0c4Dad8E18B95D844caE6FF915dF0a2BD')}`);
305
+ console.log(`${pc.green('Solana:')} ${pc.white('8J5VvZu6ayQa4XnSAAzx4YX3QsXYyyNrP4Zr7JEx1KFY')}`);
306
+ console.log(`${pc.red('TRX:')} ${pc.white('TPgZSrHUhKdjyMwaGd15utJvsbk85oBbit')}`);
307
+ console.log(pc.white(`----------------------------------------\n`));
308
+ };
309
+
310
+ // Register commands
311
+ program
312
+ .command('price <query>')
313
+ .description('Get the current price of a cryptocurrency (symbol or contract address)')
314
+ .action(handlePrice);
315
+
316
+ program
317
+ .command('info <query>')
318
+ .description('Get detailed info about a cryptocurrency (market cap, volume, FDV, etc.)')
319
+ .action(handleInfo);
320
+
321
+ program
322
+ .command('top')
323
+ .description('Show top 50 cryptocurrencies by market cap')
324
+ .action(handleTop);
325
+
326
+ program
327
+ .command('search <query>')
328
+ .description('Search for tokens or coins across multiple providers')
329
+ .action(handleSearch);
330
+
331
+ program
332
+ .command('trending')
333
+ .description('Show top 10 trending coins/tokens right now')
334
+ .action(handleTrending);
335
+
336
+ program
337
+ .command('market')
338
+ .description('Show global cryptocurrency market overview')
339
+ .action(handleMarket);
340
+
341
+ program
342
+ .command('sentiment')
343
+ .description('Show current Crypto Fear & Greed Index')
344
+ .action(handleSentiment);
345
+
346
+ program
347
+ .command('gas')
348
+ .description('Show current Ethereum network gas prices')
349
+ .action(handleGas);
350
+
351
+ program
352
+ .command('chart <query>')
353
+ .description('Draw a terminal ASCII chart of historical prices')
354
+ .option('-d, --days <days>', 'Number of days to chart', '7')
355
+ .action(handleChart);
356
+
357
+ program
358
+ .command('donate')
359
+ .description('Show donation addresses to support development')
360
+ .action(handleDonate);
361
+
362
+ async function interactiveMode() {
363
+ console.clear();
364
+ intro(pc.bgCyan(pc.black(' crypull CLI ')));
365
+
366
+ while (true) {
367
+ const action = await select({
368
+ message: 'What would you like to do?',
369
+ options: [
370
+ { value: 'price', label: '💰 Get token price' },
371
+ { value: 'info', label: 'ℹ️ Get detailed token info' },
372
+ { value: 'top', label: '🏆 Show top 50 cryptocurrencies' },
373
+ { value: 'search', label: '🔍 Search for a token' },
374
+ { value: 'trending', label: '🔥 Show trending coins' },
375
+ { value: 'market', label: '🌍 Show global market overview' },
376
+ { value: 'sentiment', label: '🎭 Show Fear & Greed Index' },
377
+ { value: 'gas', label: '⛽ Show ETH gas prices' },
378
+ { value: 'chart', label: '📈 Show historical price chart' },
379
+ { value: 'donate', label: '💖 Donate / Support' },
380
+ { value: 'exit', label: '❌ Exit' },
381
+ ]
382
+ });
383
+
384
+ if (isCancel(action) || action === 'exit') {
385
+ outro('Goodbye!');
386
+ process.exit(0);
387
+ }
388
+
389
+ if (['price', 'info', 'search', 'chart'].includes(action as string)) {
390
+ const query = await text({
391
+ message: 'Enter token symbol or contract address:',
392
+ placeholder: 'e.g., btc, ethereum, 0x...',
393
+ validate: (value) => {
394
+ if (!value) return 'Please enter a valid query.';
395
+ }
396
+ });
397
+
398
+ if (isCancel(query)) {
399
+ outro('Goodbye!');
400
+ process.exit(0);
401
+ }
402
+
403
+ if (action === 'price') await handlePrice(query as string);
404
+ else if (action === 'info') await handleInfo(query as string);
405
+ else if (action === 'search') await handleSearch(query as string);
406
+ else if (action === 'chart') {
407
+ const days = await text({
408
+ message: 'Enter number of days for the chart (default 7):',
409
+ placeholder: '7',
410
+ defaultValue: '7'
411
+ });
412
+ if (isCancel(days)) {
413
+ outro('Goodbye!');
414
+ process.exit(0);
415
+ }
416
+ await handleChart(query as string, { days: days as string });
417
+ }
418
+ } else {
419
+ if (action === 'top') await handleTop();
420
+ else if (action === 'trending') await handleTrending();
421
+ else if (action === 'market') await handleMarket();
422
+ else if (action === 'sentiment') await handleSentiment();
423
+ else if (action === 'gas') await handleGas();
424
+ else if (action === 'donate') handleDonate();
425
+ }
426
+
427
+ console.log(pc.gray('\n---\n'));
428
+ }
429
+ }
430
+
431
+ if (process.argv.length <= 2) {
432
+ // Run interactive mode
433
+ interactiveMode().catch((err) => {
434
+ console.error(pc.red('An error occurred:'), err);
435
+ process.exit(1);
436
+ });
437
+ } else {
438
+ // Use commander parsing
439
+ program.parse(process.argv);
440
+ }