@spfunctions/cli 1.7.17 → 1.7.20
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/dist/101.index.js +1 -0
- package/dist/12.index.js +1 -0
- package/dist/160.index.js +1 -0
- package/dist/174.index.js +1 -0
- package/dist/278.index.js +6 -0
- package/dist/582.index.js +1 -0
- package/dist/641.index.js +324 -0
- package/dist/669.index.js +1 -0
- package/dist/722.index.js +1 -0
- package/dist/788.index.js +1 -0
- package/dist/816.index.js +12 -0
- package/dist/830.index.js +1 -0
- package/dist/921.index.js +1 -0
- package/dist/index.js +1 -813
- package/package.json +6 -2
- package/dist/cache.d.ts +0 -6
- package/dist/cache.js +0 -31
- package/dist/cache.test.d.ts +0 -1
- package/dist/cache.test.js +0 -73
- package/dist/client.d.ts +0 -56
- package/dist/client.js +0 -205
- package/dist/client.test.d.ts +0 -1
- package/dist/client.test.js +0 -89
- package/dist/commands/agent.d.ts +0 -20
- package/dist/commands/agent.js +0 -4119
- package/dist/commands/announcements.d.ts +0 -3
- package/dist/commands/announcements.js +0 -28
- package/dist/commands/augment.d.ts +0 -12
- package/dist/commands/augment.js +0 -56
- package/dist/commands/balance.d.ts +0 -3
- package/dist/commands/balance.js +0 -17
- package/dist/commands/book.d.ts +0 -17
- package/dist/commands/book.js +0 -220
- package/dist/commands/cancel.d.ts +0 -5
- package/dist/commands/cancel.js +0 -41
- package/dist/commands/context.d.ts +0 -6
- package/dist/commands/context.js +0 -208
- package/dist/commands/create.d.ts +0 -7
- package/dist/commands/create.js +0 -42
- package/dist/commands/dashboard.d.ts +0 -14
- package/dist/commands/dashboard.js +0 -215
- package/dist/commands/delta.d.ts +0 -16
- package/dist/commands/delta.js +0 -115
- package/dist/commands/edges.d.ts +0 -26
- package/dist/commands/edges.js +0 -237
- package/dist/commands/evaluate.d.ts +0 -4
- package/dist/commands/evaluate.js +0 -30
- package/dist/commands/explore.d.ts +0 -14
- package/dist/commands/explore.js +0 -115
- package/dist/commands/feed.d.ts +0 -13
- package/dist/commands/feed.js +0 -73
- package/dist/commands/fills.d.ts +0 -4
- package/dist/commands/fills.js +0 -29
- package/dist/commands/forecast.d.ts +0 -4
- package/dist/commands/forecast.js +0 -53
- package/dist/commands/get.d.ts +0 -5
- package/dist/commands/get.js +0 -98
- package/dist/commands/heartbeat.d.ts +0 -20
- package/dist/commands/heartbeat.js +0 -73
- package/dist/commands/history.d.ts +0 -3
- package/dist/commands/history.js +0 -38
- package/dist/commands/liquidity.d.ts +0 -14
- package/dist/commands/liquidity.js +0 -378
- package/dist/commands/list.d.ts +0 -5
- package/dist/commands/list.js +0 -38
- package/dist/commands/login.d.ts +0 -9
- package/dist/commands/login.js +0 -98
- package/dist/commands/markets.d.ts +0 -10
- package/dist/commands/markets.js +0 -39
- package/dist/commands/milestones.d.ts +0 -8
- package/dist/commands/milestones.js +0 -56
- package/dist/commands/orders.d.ts +0 -4
- package/dist/commands/orders.js +0 -28
- package/dist/commands/performance.d.ts +0 -11
- package/dist/commands/performance.js +0 -250
- package/dist/commands/positions.d.ts +0 -19
- package/dist/commands/positions.js +0 -294
- package/dist/commands/prompt.d.ts +0 -13
- package/dist/commands/prompt.js +0 -35
- package/dist/commands/publish.d.ts +0 -15
- package/dist/commands/publish.js +0 -39
- package/dist/commands/query.d.ts +0 -15
- package/dist/commands/query.js +0 -132
- package/dist/commands/rfq.d.ts +0 -5
- package/dist/commands/rfq.js +0 -35
- package/dist/commands/scan.d.ts +0 -11
- package/dist/commands/scan.js +0 -230
- package/dist/commands/schedule.d.ts +0 -3
- package/dist/commands/schedule.js +0 -38
- package/dist/commands/settlements.d.ts +0 -6
- package/dist/commands/settlements.js +0 -50
- package/dist/commands/setup.d.ts +0 -24
- package/dist/commands/setup.js +0 -700
- package/dist/commands/signal.d.ts +0 -6
- package/dist/commands/signal.js +0 -32
- package/dist/commands/strategies.d.ts +0 -11
- package/dist/commands/strategies.js +0 -130
- package/dist/commands/telegram.d.ts +0 -15
- package/dist/commands/telegram.js +0 -125
- package/dist/commands/trade.d.ts +0 -12
- package/dist/commands/trade.js +0 -112
- package/dist/commands/watch.d.ts +0 -19
- package/dist/commands/watch.js +0 -157
- package/dist/commands/whatif.d.ts +0 -17
- package/dist/commands/whatif.js +0 -209
- package/dist/commands/x.d.ts +0 -28
- package/dist/commands/x.js +0 -167
- package/dist/config.d.ts +0 -55
- package/dist/config.js +0 -139
- package/dist/config.test.d.ts +0 -1
- package/dist/config.test.js +0 -138
- package/dist/index.d.ts +0 -20
- package/dist/kalshi.d.ts +0 -144
- package/dist/kalshi.js +0 -498
- package/dist/polymarket.d.ts +0 -237
- package/dist/polymarket.js +0 -353
- package/dist/polymarket.test.d.ts +0 -1
- package/dist/polymarket.test.js +0 -424
- package/dist/share.d.ts +0 -4
- package/dist/share.js +0 -27
- package/dist/skills/loader.d.ts +0 -19
- package/dist/skills/loader.js +0 -86
- package/dist/telegram/agent-bridge.d.ts +0 -15
- package/dist/telegram/agent-bridge.js +0 -573
- package/dist/telegram/bot.d.ts +0 -10
- package/dist/telegram/bot.js +0 -297
- package/dist/telegram/commands.d.ts +0 -11
- package/dist/telegram/commands.js +0 -120
- package/dist/telegram/format.d.ts +0 -11
- package/dist/telegram/format.js +0 -51
- package/dist/telegram/format.test.d.ts +0 -1
- package/dist/telegram/format.test.js +0 -73
- package/dist/telegram/poller.d.ts +0 -6
- package/dist/telegram/poller.js +0 -32
- package/dist/topics.d.ts +0 -17
- package/dist/topics.js +0 -102
- package/dist/topics.test.d.ts +0 -1
- package/dist/topics.test.js +0 -131
- package/dist/tui/border.d.ts +0 -33
- package/dist/tui/border.js +0 -87
- package/dist/tui/chart.d.ts +0 -19
- package/dist/tui/chart.js +0 -117
- package/dist/tui/dashboard.d.ts +0 -9
- package/dist/tui/dashboard.js +0 -814
- package/dist/tui/layout.d.ts +0 -16
- package/dist/tui/layout.js +0 -41
- package/dist/tui/screen.d.ts +0 -33
- package/dist/tui/screen.js +0 -102
- package/dist/tui/state.d.ts +0 -40
- package/dist/tui/state.js +0 -36
- package/dist/tui/widgets/commandbar.d.ts +0 -8
- package/dist/tui/widgets/commandbar.js +0 -82
- package/dist/tui/widgets/detail.d.ts +0 -9
- package/dist/tui/widgets/detail.js +0 -151
- package/dist/tui/widgets/edges.d.ts +0 -4
- package/dist/tui/widgets/edges.js +0 -34
- package/dist/tui/widgets/liquidity.d.ts +0 -9
- package/dist/tui/widgets/liquidity.js +0 -142
- package/dist/tui/widgets/orders.d.ts +0 -4
- package/dist/tui/widgets/orders.js +0 -37
- package/dist/tui/widgets/portfolio.d.ts +0 -4
- package/dist/tui/widgets/portfolio.js +0 -59
- package/dist/tui/widgets/signals.d.ts +0 -4
- package/dist/tui/widgets/signals.js +0 -31
- package/dist/tui/widgets/statusbar.d.ts +0 -8
- package/dist/tui/widgets/statusbar.js +0 -72
- package/dist/tui/widgets/thesis.d.ts +0 -4
- package/dist/tui/widgets/thesis.js +0 -66
- package/dist/tui/widgets/trade.d.ts +0 -9
- package/dist/tui/widgets/trade.js +0 -117
- package/dist/tui/widgets/upcoming.d.ts +0 -4
- package/dist/tui/widgets/upcoming.js +0 -41
- package/dist/tui/widgets/whatif.d.ts +0 -7
- package/dist/tui/widgets/whatif.js +0 -113
- package/dist/types/output.d.ts +0 -412
- package/dist/types/output.js +0 -9
- package/dist/utils.d.ts +0 -42
- package/dist/utils.js +0 -124
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -111
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* sf liquidity — Market liquidity scanner by topic and horizon
|
|
4
|
-
*
|
|
5
|
-
* Scans known series, fetches public orderbooks, and displays
|
|
6
|
-
* spread/depth/slippage data grouped by topic and horizon.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.liquidityCommand = liquidityCommand;
|
|
10
|
-
const client_js_1 = require("../client.js");
|
|
11
|
-
const kalshi_js_1 = require("../kalshi.js");
|
|
12
|
-
const polymarket_js_1 = require("../polymarket.js");
|
|
13
|
-
const topics_js_1 = require("../topics.js");
|
|
14
|
-
const utils_js_1 = require("../utils.js");
|
|
15
|
-
// ── Horizon classification ───────────────────────────────────────────────────
|
|
16
|
-
function classifyHorizon(closeTime) {
|
|
17
|
-
const now = Date.now();
|
|
18
|
-
const close = new Date(closeTime).getTime();
|
|
19
|
-
const daysAway = (close - now) / (1000 * 60 * 60 * 24);
|
|
20
|
-
if (daysAway < 7)
|
|
21
|
-
return 'weekly';
|
|
22
|
-
if (daysAway <= 35)
|
|
23
|
-
return 'monthly';
|
|
24
|
-
return 'long-term';
|
|
25
|
-
}
|
|
26
|
-
function horizonLabel(h) {
|
|
27
|
-
switch (h) {
|
|
28
|
-
case 'weekly': return 'weekly (<7d)';
|
|
29
|
-
case 'monthly': return 'monthly (7-35d)';
|
|
30
|
-
case 'long-term': return 'long-term (>35d)';
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// ── Slippage calculation ─────────────────────────────────────────────────────
|
|
34
|
-
/**
|
|
35
|
-
* Calculate weighted average price to buy `qty` YES contracts
|
|
36
|
-
* by eating NO bids (selling NO = buying YES).
|
|
37
|
-
*
|
|
38
|
-
* no_dollars are sorted low→high by price. The best NO bid
|
|
39
|
-
* (highest price) is the cheapest YES ask.
|
|
40
|
-
*/
|
|
41
|
-
function calcSlippage100(noDollars, qty) {
|
|
42
|
-
// Sort descending by price (highest no bid = cheapest yes ask)
|
|
43
|
-
const levels = noDollars
|
|
44
|
-
.map(([price, amount]) => ({
|
|
45
|
-
noPrice: parseFloat(price),
|
|
46
|
-
yesAsk: 1.0 - parseFloat(price),
|
|
47
|
-
qty: parseFloat(amount),
|
|
48
|
-
}))
|
|
49
|
-
.filter(l => l.noPrice > 0 && l.qty > 0)
|
|
50
|
-
.sort((a, b) => b.noPrice - a.noPrice); // highest no price first = lowest yes ask
|
|
51
|
-
let remaining = qty;
|
|
52
|
-
let totalCost = 0;
|
|
53
|
-
for (const level of levels) {
|
|
54
|
-
if (remaining <= 0)
|
|
55
|
-
break;
|
|
56
|
-
const fill = Math.min(remaining, level.qty);
|
|
57
|
-
totalCost += fill * level.yesAsk;
|
|
58
|
-
remaining -= fill;
|
|
59
|
-
}
|
|
60
|
-
if (remaining > 0)
|
|
61
|
-
return '∞';
|
|
62
|
-
const avgPrice = totalCost / qty;
|
|
63
|
-
return (avgPrice * 100).toFixed(1) + '¢';
|
|
64
|
-
}
|
|
65
|
-
// ── Batch concurrency helper ─────────────────────────────────────────────────
|
|
66
|
-
async function batchProcess(items, fn, batchSize, delayMs) {
|
|
67
|
-
const results = [];
|
|
68
|
-
for (let i = 0; i < items.length; i += batchSize) {
|
|
69
|
-
const batch = items.slice(i, i + batchSize);
|
|
70
|
-
const settled = await Promise.allSettled(batch.map(fn));
|
|
71
|
-
for (const s of settled) {
|
|
72
|
-
results.push(s.status === 'fulfilled' ? s.value : null);
|
|
73
|
-
}
|
|
74
|
-
if (i + batchSize < items.length) {
|
|
75
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return results;
|
|
79
|
-
}
|
|
80
|
-
// ── Main command ─────────────────────────────────────────────────────────────
|
|
81
|
-
async function liquidityCommand(opts) {
|
|
82
|
-
const allTopics = Object.keys(topics_js_1.TOPIC_SERIES);
|
|
83
|
-
// If no topic and no --all, show available topics
|
|
84
|
-
if (!opts.topic && !opts.all) {
|
|
85
|
-
console.log();
|
|
86
|
-
console.log(`${utils_js_1.c.bold}Available Topics${utils_js_1.c.reset} ${utils_js_1.c.dim}(${allTopics.length} topics)${utils_js_1.c.reset}`);
|
|
87
|
-
console.log(utils_js_1.c.dim + '─'.repeat(50) + utils_js_1.c.reset);
|
|
88
|
-
console.log();
|
|
89
|
-
for (const topic of allTopics) {
|
|
90
|
-
const series = topics_js_1.TOPIC_SERIES[topic];
|
|
91
|
-
console.log(` ${utils_js_1.c.cyan}${(0, utils_js_1.pad)(topic, 14)}${utils_js_1.c.reset} ${utils_js_1.c.dim}${series.slice(0, 3).join(', ')}${series.length > 3 ? ` +${series.length - 3} more` : ''}${utils_js_1.c.reset}`);
|
|
92
|
-
}
|
|
93
|
-
console.log();
|
|
94
|
-
console.log(`${utils_js_1.c.dim}Usage: sf liquidity <topic> (e.g. sf liquidity oil)${utils_js_1.c.reset}`);
|
|
95
|
-
console.log(`${utils_js_1.c.dim} sf liquidity --all (scan all topics)${utils_js_1.c.reset}`);
|
|
96
|
-
console.log();
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
// Determine which topics to scan
|
|
100
|
-
const topics = opts.topic
|
|
101
|
-
? allTopics.filter(t => t.toLowerCase() === opts.topic.toLowerCase())
|
|
102
|
-
: allTopics;
|
|
103
|
-
if (opts.topic && topics.length === 0) {
|
|
104
|
-
const valid = allTopics.join(', ');
|
|
105
|
-
console.error(`Unknown topic: ${opts.topic}. Valid topics: ${valid}`);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
// Fetch held positions if Kalshi is configured
|
|
109
|
-
let heldTickers = new Set();
|
|
110
|
-
if ((0, kalshi_js_1.isKalshiConfigured)()) {
|
|
111
|
-
try {
|
|
112
|
-
const positions = await (0, kalshi_js_1.getPositions)();
|
|
113
|
-
if (positions) {
|
|
114
|
-
heldTickers = new Set(positions.map(p => p.ticker));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// ignore — positions are optional decoration
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
// Collect all markets per topic
|
|
122
|
-
const topicMarkets = {};
|
|
123
|
-
for (const topic of topics) {
|
|
124
|
-
const series = topics_js_1.TOPIC_SERIES[topic];
|
|
125
|
-
const markets = [];
|
|
126
|
-
for (const seriesTicker of series) {
|
|
127
|
-
try {
|
|
128
|
-
const m = await (0, client_js_1.kalshiFetchMarketsBySeries)(seriesTicker);
|
|
129
|
-
markets.push(...m);
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
// skip failed series
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (markets.length > 0) {
|
|
136
|
-
topicMarkets[topic] = markets;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// ── Polymarket: search for matching markets ──
|
|
140
|
-
const scanPoly = opts.venue !== 'kalshi';
|
|
141
|
-
if (scanPoly) {
|
|
142
|
-
for (const topic of topics) {
|
|
143
|
-
try {
|
|
144
|
-
const events = await (0, polymarket_js_1.polymarketSearch)(topic, 5);
|
|
145
|
-
for (const event of events) {
|
|
146
|
-
for (const m of (event.markets || [])) {
|
|
147
|
-
if (!m.active || m.closed || !m.enableOrderBook)
|
|
148
|
-
continue;
|
|
149
|
-
if (!topicMarkets[topic])
|
|
150
|
-
topicMarkets[topic] = [];
|
|
151
|
-
topicMarkets[topic].push({
|
|
152
|
-
ticker: m.conditionId?.slice(0, 16) || m.id,
|
|
153
|
-
close_time: m.endDateIso || '',
|
|
154
|
-
venue: 'polymarket',
|
|
155
|
-
question: m.question || event.title,
|
|
156
|
-
clobTokenIds: m.clobTokenIds,
|
|
157
|
-
bestBid: m.bestBid,
|
|
158
|
-
bestAsk: m.bestAsk,
|
|
159
|
-
spread: m.spread,
|
|
160
|
-
liquidityNum: m.liquidityNum,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
// skip Polymarket errors
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Filter by horizon if specified, classify all markets
|
|
171
|
-
const horizonFilter = opts.horizon;
|
|
172
|
-
const marketInfos = [];
|
|
173
|
-
for (const [topic, markets] of Object.entries(topicMarkets)) {
|
|
174
|
-
for (const m of markets) {
|
|
175
|
-
const closeTime = m.close_time || m.expiration_time || '';
|
|
176
|
-
if (!closeTime)
|
|
177
|
-
continue;
|
|
178
|
-
const horizon = classifyHorizon(closeTime);
|
|
179
|
-
if (horizonFilter && horizon !== horizonFilter)
|
|
180
|
-
continue;
|
|
181
|
-
marketInfos.push({
|
|
182
|
-
ticker: m.ticker,
|
|
183
|
-
closeTime,
|
|
184
|
-
topic,
|
|
185
|
-
horizon,
|
|
186
|
-
venue: m.venue || 'kalshi',
|
|
187
|
-
question: m.question,
|
|
188
|
-
clobTokenIds: m.clobTokenIds,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (marketInfos.length === 0) {
|
|
193
|
-
console.log('No markets found matching filters.');
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
// Split by venue
|
|
197
|
-
const kalshiInfos = marketInfos.filter(m => m.venue === 'kalshi');
|
|
198
|
-
const polyInfos = marketInfos.filter(m => m.venue === 'polymarket');
|
|
199
|
-
// Fetch Kalshi orderbooks in batches
|
|
200
|
-
const kalshiResults = await batchProcess(kalshiInfos, async (info) => {
|
|
201
|
-
const ob = await (0, kalshi_js_1.getPublicOrderbook)(info.ticker);
|
|
202
|
-
return { info, ob };
|
|
203
|
-
}, 5, 100);
|
|
204
|
-
// Fetch Polymarket orderbooks in batches
|
|
205
|
-
const polyResults = await batchProcess(polyInfos, async (info) => {
|
|
206
|
-
if (!info.clobTokenIds)
|
|
207
|
-
return { info, depth: null };
|
|
208
|
-
const ids = (0, polymarket_js_1.parseClobTokenIds)(info.clobTokenIds);
|
|
209
|
-
if (!ids)
|
|
210
|
-
return { info, depth: null };
|
|
211
|
-
const depth = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)(ids[0]);
|
|
212
|
-
return { info, depth };
|
|
213
|
-
}, 5, 100);
|
|
214
|
-
// Build liquidity rows
|
|
215
|
-
const rows = [];
|
|
216
|
-
// Process Kalshi results
|
|
217
|
-
for (const result of kalshiResults) {
|
|
218
|
-
if (!result || !result.ob)
|
|
219
|
-
continue;
|
|
220
|
-
const { info, ob } = result;
|
|
221
|
-
const yesDollars = ob.yes_dollars.map(([p, q]) => ({
|
|
222
|
-
price: Math.round(parseFloat(p) * 100),
|
|
223
|
-
qty: parseFloat(q),
|
|
224
|
-
})).filter((l) => l.price > 0);
|
|
225
|
-
const noDollars = ob.no_dollars.map(([p, q]) => ({
|
|
226
|
-
price: Math.round(parseFloat(p) * 100),
|
|
227
|
-
qty: parseFloat(q),
|
|
228
|
-
})).filter((l) => l.price > 0);
|
|
229
|
-
yesDollars.sort((a, b) => b.price - a.price);
|
|
230
|
-
noDollars.sort((a, b) => b.price - a.price);
|
|
231
|
-
const bestBid = yesDollars.length > 0 ? yesDollars[0].price : 0;
|
|
232
|
-
const bestAsk = noDollars.length > 0 ? (100 - noDollars[0].price) : 100;
|
|
233
|
-
const spread = bestAsk - bestBid;
|
|
234
|
-
const bidDepth = yesDollars.reduce((sum, l) => sum + l.qty, 0);
|
|
235
|
-
const askDepth = noDollars.reduce((sum, l) => sum + l.qty, 0);
|
|
236
|
-
const slippage100 = calcSlippage100(ob.no_dollars, 100);
|
|
237
|
-
if (opts.minDepth && (bidDepth + askDepth) < opts.minDepth)
|
|
238
|
-
continue;
|
|
239
|
-
rows.push({
|
|
240
|
-
ticker: info.ticker,
|
|
241
|
-
shortTicker: info.ticker,
|
|
242
|
-
topic: info.topic.toUpperCase(),
|
|
243
|
-
horizon: info.horizon,
|
|
244
|
-
closeTime: info.closeTime,
|
|
245
|
-
bestBid, bestAsk, spread, bidDepth, askDepth, slippage100,
|
|
246
|
-
held: heldTickers.has(info.ticker),
|
|
247
|
-
venue: 'kalshi',
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
// Process Polymarket results
|
|
251
|
-
for (const result of polyResults) {
|
|
252
|
-
if (!result || !result.depth)
|
|
253
|
-
continue;
|
|
254
|
-
const { info, depth } = result;
|
|
255
|
-
if (opts.minDepth && (depth.bidDepthTop3 + depth.askDepthTop3) < opts.minDepth)
|
|
256
|
-
continue;
|
|
257
|
-
rows.push({
|
|
258
|
-
ticker: (info.question || info.ticker).slice(0, 30),
|
|
259
|
-
shortTicker: (info.question || info.ticker).slice(0, 30),
|
|
260
|
-
topic: info.topic.toUpperCase(),
|
|
261
|
-
horizon: info.horizon,
|
|
262
|
-
closeTime: info.closeTime,
|
|
263
|
-
bestBid: depth.bestBid,
|
|
264
|
-
bestAsk: depth.bestAsk,
|
|
265
|
-
spread: depth.spread,
|
|
266
|
-
bidDepth: depth.totalBidDepth,
|
|
267
|
-
askDepth: depth.totalAskDepth,
|
|
268
|
-
slippage100: '-',
|
|
269
|
-
held: false,
|
|
270
|
-
venue: 'polymarket',
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
// ── JSON output ────────────────────────────────────────────────────────────
|
|
274
|
-
if (opts.json) {
|
|
275
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
// ── Formatted output ───────────────────────────────────────────────────────
|
|
279
|
-
const now = new Date().toISOString().slice(0, 10);
|
|
280
|
-
console.log();
|
|
281
|
-
console.log(`${utils_js_1.c.bold}Liquidity Scanner${utils_js_1.c.reset} ${utils_js_1.c.dim}(${now} UTC)${utils_js_1.c.reset}`);
|
|
282
|
-
console.log(utils_js_1.c.dim + '─'.repeat(68) + utils_js_1.c.reset);
|
|
283
|
-
// Group rows by topic → horizon (topic is pre-set on each row)
|
|
284
|
-
const grouped = {};
|
|
285
|
-
for (const row of rows) {
|
|
286
|
-
const topic = row.topic || 'OTHER';
|
|
287
|
-
if (!grouped[topic])
|
|
288
|
-
grouped[topic] = {};
|
|
289
|
-
if (!grouped[topic][row.horizon])
|
|
290
|
-
grouped[topic][row.horizon] = [];
|
|
291
|
-
grouped[topic][row.horizon].push(row);
|
|
292
|
-
}
|
|
293
|
-
let totalMarkets = 0;
|
|
294
|
-
let thinMarkets = 0;
|
|
295
|
-
let heldCount = 0;
|
|
296
|
-
const horizonOrder = ['weekly', 'monthly', 'long-term'];
|
|
297
|
-
for (const [topic, horizons] of Object.entries(grouped)) {
|
|
298
|
-
for (const h of horizonOrder) {
|
|
299
|
-
const marketRows = horizons[h];
|
|
300
|
-
if (!marketRows || marketRows.length === 0)
|
|
301
|
-
continue;
|
|
302
|
-
// Abbreviate Kalshi tickers (strip common prefix), keep Polymarket titles as-is
|
|
303
|
-
const kalshiRows = marketRows.filter(r => r.venue === 'kalshi');
|
|
304
|
-
if (kalshiRows.length > 1) {
|
|
305
|
-
const commonPrefix = findCommonPrefix(kalshiRows.map(r => r.ticker));
|
|
306
|
-
for (const row of kalshiRows) {
|
|
307
|
-
const short = commonPrefix.length > 0
|
|
308
|
-
? row.ticker.slice(commonPrefix.length).replace(/^-/, '')
|
|
309
|
-
: row.ticker;
|
|
310
|
-
row.shortTicker = short.length > 0 ? short : row.ticker;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// Sort: Kalshi by ticker, Polymarket by spread
|
|
314
|
-
marketRows.sort((a, b) => {
|
|
315
|
-
if (a.venue !== b.venue)
|
|
316
|
-
return a.venue === 'kalshi' ? -1 : 1;
|
|
317
|
-
return a.venue === 'kalshi'
|
|
318
|
-
? a.ticker.localeCompare(b.ticker)
|
|
319
|
-
: a.spread - b.spread;
|
|
320
|
-
});
|
|
321
|
-
console.log();
|
|
322
|
-
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}${topic}${utils_js_1.c.reset} ${utils_js_1.c.dim}— ${horizonLabel(h)}${utils_js_1.c.reset}`);
|
|
323
|
-
console.log(`${utils_js_1.c.dim} ${(0, utils_js_1.pad)('Market', 22)} ${(0, utils_js_1.rpad)('Bid¢', 5)} ${(0, utils_js_1.rpad)('Ask¢', 5)} ${(0, utils_js_1.rpad)('Spread', 6)} ${(0, utils_js_1.rpad)('BidDep', 6)} ${(0, utils_js_1.rpad)('AskDep', 6)} ${(0, utils_js_1.rpad)('Slip100', 7)}${utils_js_1.c.reset}`);
|
|
324
|
-
for (const row of marketRows) {
|
|
325
|
-
totalMarkets++;
|
|
326
|
-
if (row.held)
|
|
327
|
-
heldCount++;
|
|
328
|
-
const thin = row.spread > 5;
|
|
329
|
-
if (thin)
|
|
330
|
-
thinMarkets++;
|
|
331
|
-
// Color spread
|
|
332
|
-
let spreadStr;
|
|
333
|
-
if (row.spread <= 2) {
|
|
334
|
-
spreadStr = `${utils_js_1.c.green}${row.spread}¢${utils_js_1.c.reset}`;
|
|
335
|
-
}
|
|
336
|
-
else if (row.spread <= 5) {
|
|
337
|
-
spreadStr = `${utils_js_1.c.yellow}${row.spread}¢${utils_js_1.c.reset}`;
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
spreadStr = `${utils_js_1.c.red}${row.spread}¢${utils_js_1.c.reset}`;
|
|
341
|
-
}
|
|
342
|
-
const thinMark = thin ? ' \u26A0\uFE0F' : '';
|
|
343
|
-
const heldMark = row.held ? ` ${utils_js_1.c.magenta}\u2190 held${utils_js_1.c.reset}` : '';
|
|
344
|
-
// Pad spread field accounting for ANSI codes
|
|
345
|
-
const spreadPadded = (0, utils_js_1.rpad)(`${row.spread}¢`, 6);
|
|
346
|
-
const spreadColored = row.spread <= 2
|
|
347
|
-
? `${utils_js_1.c.green}${spreadPadded}${utils_js_1.c.reset}`
|
|
348
|
-
: row.spread <= 5
|
|
349
|
-
? `${utils_js_1.c.yellow}${spreadPadded}${utils_js_1.c.reset}`
|
|
350
|
-
: `${utils_js_1.c.red}${spreadPadded}${utils_js_1.c.reset}`;
|
|
351
|
-
const venueTag = row.venue === 'polymarket' ? `${utils_js_1.c.blue}POLY${utils_js_1.c.reset} ` : `${utils_js_1.c.cyan}KLSH${utils_js_1.c.reset} `;
|
|
352
|
-
console.log(`${venueTag}${(0, utils_js_1.pad)(row.shortTicker, 22)} ${(0, utils_js_1.rpad)(String(row.bestBid), 5)} ${(0, utils_js_1.rpad)(String(row.bestAsk), 5)} ${spreadColored} ${(0, utils_js_1.rpad)(String(Math.round(row.bidDepth)), 6)} ${(0, utils_js_1.rpad)(String(Math.round(row.askDepth)), 6)} ${(0, utils_js_1.rpad)(row.slippage100, 7)}${thinMark}${heldMark}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// Summary
|
|
357
|
-
console.log();
|
|
358
|
-
console.log(`${utils_js_1.c.dim}Summary: ${totalMarkets} markets | ${thinMarkets} thin (spread>5¢) | ${heldCount} held${utils_js_1.c.reset}`);
|
|
359
|
-
console.log();
|
|
360
|
-
}
|
|
361
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
362
|
-
function findCommonPrefix(strings) {
|
|
363
|
-
if (strings.length === 0)
|
|
364
|
-
return '';
|
|
365
|
-
if (strings.length === 1)
|
|
366
|
-
return '';
|
|
367
|
-
let prefix = strings[0];
|
|
368
|
-
for (let i = 1; i < strings.length; i++) {
|
|
369
|
-
while (!strings[i].startsWith(prefix)) {
|
|
370
|
-
prefix = prefix.slice(0, -1);
|
|
371
|
-
if (prefix.length === 0)
|
|
372
|
-
return '';
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
// Don't strip if it would leave nothing for some tickers
|
|
376
|
-
// Also strip trailing hyphen from prefix for cleaner display
|
|
377
|
-
return prefix;
|
|
378
|
-
}
|
package/dist/commands/list.d.ts
DELETED
package/dist/commands/list.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listCommand = listCommand;
|
|
4
|
-
const client_js_1 = require("../client.js");
|
|
5
|
-
const utils_js_1 = require("../utils.js");
|
|
6
|
-
async function listCommand(opts) {
|
|
7
|
-
const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
|
|
8
|
-
const { theses } = await client.listTheses();
|
|
9
|
-
if (opts.json) {
|
|
10
|
-
console.log(JSON.stringify(theses, null, 2));
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
if (theses.length === 0) {
|
|
14
|
-
console.log(`${utils_js_1.c.dim}No theses found.${utils_js_1.c.reset}`);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
console.log(`\n${utils_js_1.c.bold}` +
|
|
18
|
-
(0, utils_js_1.pad)('ID', 12) +
|
|
19
|
-
(0, utils_js_1.pad)('Status', 10) +
|
|
20
|
-
(0, utils_js_1.rpad)('Conf', 6) +
|
|
21
|
-
' ' +
|
|
22
|
-
(0, utils_js_1.pad)('Updated', 14) +
|
|
23
|
-
' Title' +
|
|
24
|
-
utils_js_1.c.reset);
|
|
25
|
-
(0, utils_js_1.hr)(90);
|
|
26
|
-
for (const t of theses) {
|
|
27
|
-
const statusColor = t.status === 'active' ? utils_js_1.c.green : t.status === 'forming' ? utils_js_1.c.yellow : utils_js_1.c.dim;
|
|
28
|
-
const conf = t.confidence ? (0, utils_js_1.pct)(parseFloat(t.confidence)) : '-';
|
|
29
|
-
console.log((0, utils_js_1.pad)((0, utils_js_1.shortId)(t.id), 12) +
|
|
30
|
-
statusColor + (0, utils_js_1.pad)(t.status, 10) + utils_js_1.c.reset +
|
|
31
|
-
(0, utils_js_1.rpad)(conf, 6) +
|
|
32
|
-
' ' +
|
|
33
|
-
utils_js_1.c.dim + (0, utils_js_1.pad)((0, utils_js_1.shortDate)(t.updatedAt), 14) + utils_js_1.c.reset +
|
|
34
|
-
' ' +
|
|
35
|
-
(0, utils_js_1.trunc)(t.title || t.rawThesis.slice(0, 60), 50));
|
|
36
|
-
}
|
|
37
|
-
console.log(`\n${utils_js_1.c.dim}${theses.length} thesis(es)${utils_js_1.c.reset}`);
|
|
38
|
-
}
|
package/dist/commands/login.d.ts
DELETED
package/dist/commands/login.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* sf login — Browser-based authentication.
|
|
4
|
-
*
|
|
5
|
-
* Opens the browser, user logs in via magic link, CLI receives an API key.
|
|
6
|
-
* Zero manual key configuration needed.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.loginCommand = loginCommand;
|
|
10
|
-
const crypto_1 = require("crypto");
|
|
11
|
-
const config_js_1 = require("../config.js");
|
|
12
|
-
const utils_js_1 = require("../utils.js");
|
|
13
|
-
const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
|
|
14
|
-
async function loginCommand(opts) {
|
|
15
|
-
const apiUrl = opts.apiUrl || (0, config_js_1.loadConfig)().apiUrl || SF_API_URL;
|
|
16
|
-
// Only block if the CONFIG FILE has an API key (not env vars)
|
|
17
|
-
const fileConfig = (0, config_js_1.loadFileConfig)();
|
|
18
|
-
if (fileConfig.apiKey) {
|
|
19
|
-
console.log(`\n ${utils_js_1.c.dim}Already configured with API key ${fileConfig.apiKey.slice(0, 12)}...${utils_js_1.c.reset}`);
|
|
20
|
-
console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf setup --reset${utils_js_1.c.dim} first to reconfigure.${utils_js_1.c.reset}\n`);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
// Generate session token
|
|
24
|
-
const sessionToken = (0, crypto_1.randomBytes)(32).toString('base64url');
|
|
25
|
-
// Register session on server
|
|
26
|
-
console.log(`\n ${utils_js_1.c.dim}Registering login session...${utils_js_1.c.reset}`);
|
|
27
|
-
try {
|
|
28
|
-
const res = await fetch(`${apiUrl}/api/auth/cli`, {
|
|
29
|
-
method: 'POST',
|
|
30
|
-
headers: { 'Content-Type': 'application/json' },
|
|
31
|
-
body: JSON.stringify({ sessionToken }),
|
|
32
|
-
});
|
|
33
|
-
if (!res.ok) {
|
|
34
|
-
const data = await res.json().catch(() => ({}));
|
|
35
|
-
console.error(` ${utils_js_1.c.red}Failed to create session: ${data.error || res.status}${utils_js_1.c.reset}`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
console.error(` ${utils_js_1.c.red}Could not reach ${apiUrl}${utils_js_1.c.reset}`);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
// Open browser
|
|
44
|
-
const loginUrl = `${apiUrl}/auth/cli?token=${sessionToken}`;
|
|
45
|
-
console.log(`\n ${utils_js_1.c.bold}Opening browser...${utils_js_1.c.reset}`);
|
|
46
|
-
console.log(` ${utils_js_1.c.dim}${loginUrl}${utils_js_1.c.reset}\n`);
|
|
47
|
-
try {
|
|
48
|
-
const { exec } = await import('child_process');
|
|
49
|
-
const platform = process.platform;
|
|
50
|
-
const cmd = platform === 'darwin' ? 'open'
|
|
51
|
-
: platform === 'win32' ? 'start'
|
|
52
|
-
: 'xdg-open';
|
|
53
|
-
exec(`${cmd} "${loginUrl}"`);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
console.log(` ${utils_js_1.c.dim}Could not open browser. Visit the URL above manually.${utils_js_1.c.reset}`);
|
|
57
|
-
}
|
|
58
|
-
// Poll for completion
|
|
59
|
-
console.log(` ${utils_js_1.c.dim}Waiting for login...${utils_js_1.c.reset}`);
|
|
60
|
-
const maxWait = 5 * 60 * 1000; // 5 minutes
|
|
61
|
-
const pollInterval = 2000;
|
|
62
|
-
const startTime = Date.now();
|
|
63
|
-
while (Date.now() - startTime < maxWait) {
|
|
64
|
-
await new Promise(r => setTimeout(r, pollInterval));
|
|
65
|
-
try {
|
|
66
|
-
const res = await fetch(`${apiUrl}/api/auth/cli/poll?token=${sessionToken}`);
|
|
67
|
-
const data = await res.json();
|
|
68
|
-
if (data.status === 'ready' && data.apiKey) {
|
|
69
|
-
// Save to config
|
|
70
|
-
(0, config_js_1.saveConfig)({
|
|
71
|
-
...fileConfig,
|
|
72
|
-
apiKey: data.apiKey,
|
|
73
|
-
apiUrl: apiUrl !== SF_API_URL ? apiUrl : undefined,
|
|
74
|
-
});
|
|
75
|
-
console.log(`\n ${utils_js_1.c.green}✓${utils_js_1.c.reset} ${utils_js_1.c.bold}Authenticated!${utils_js_1.c.reset}`);
|
|
76
|
-
console.log(` ${utils_js_1.c.dim}API key saved to ~/.sf/config.json${utils_js_1.c.reset}`);
|
|
77
|
-
console.log(`\n ${utils_js_1.c.dim}OpenRouter & Tavily are proxied through ${apiUrl}${utils_js_1.c.reset}`);
|
|
78
|
-
console.log(` ${utils_js_1.c.dim}No additional API keys needed.${utils_js_1.c.reset}`);
|
|
79
|
-
console.log(`\n ${utils_js_1.c.cyan}sf context${utils_js_1.c.reset} ${utils_js_1.c.dim}market intelligence${utils_js_1.c.reset}`);
|
|
80
|
-
console.log(` ${utils_js_1.c.cyan}sf agent${utils_js_1.c.reset} ${utils_js_1.c.dim}<thesis>${utils_js_1.c.reset} ${utils_js_1.c.dim}start the agent${utils_js_1.c.reset}`);
|
|
81
|
-
console.log(` ${utils_js_1.c.cyan}sf setup --check${utils_js_1.c.reset} ${utils_js_1.c.dim}verify config${utils_js_1.c.reset}`);
|
|
82
|
-
console.log();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (data.status === 'expired') {
|
|
86
|
-
console.error(`\n ${utils_js_1.c.red}Session expired. Run ${utils_js_1.c.cyan}sf login${utils_js_1.c.red} again.${utils_js_1.c.reset}\n`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// status === 'pending' — keep polling
|
|
90
|
-
process.stdout.write('.');
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
// Network error — keep trying
|
|
94
|
-
process.stdout.write('x');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
console.error(`\n ${utils_js_1.c.red}Timed out waiting for login. Run ${utils_js_1.c.cyan}sf login${utils_js_1.c.red} again.${utils_js_1.c.reset}\n`);
|
|
98
|
-
}
|
package/dist/commands/markets.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* sf markets
|
|
4
|
-
*
|
|
5
|
-
* Traditional market snapshot via Databento (SPY, VIX, TLT, Gold, Oil).
|
|
6
|
-
* No auth required — calls public endpoint.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.marketsCommand = marketsCommand;
|
|
10
|
-
const share_js_1 = require("../share.js");
|
|
11
|
-
const BASE_URL = 'https://simplefunctions.dev';
|
|
12
|
-
async function marketsCommand(opts) {
|
|
13
|
-
const res = await fetch(`${BASE_URL}/api/public/markets`);
|
|
14
|
-
if (!res.ok) {
|
|
15
|
-
console.error(` Error: ${res.status} ${await res.text()}`);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const data = await res.json();
|
|
19
|
-
if (opts?.share) {
|
|
20
|
-
await (0, share_js_1.shareOutput)('markets', '', data);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (opts?.json) {
|
|
24
|
-
console.log(JSON.stringify(data, null, 2));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
console.log(`\n \x1b[1mTraditional Markets\x1b[22m \x1b[2m${data.snapshotAt?.slice(0, 10) || ''}\x1b[22m\n`);
|
|
28
|
-
if (!data.markets?.length) {
|
|
29
|
-
console.log(' No data available.\n');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
for (const m of data.markets) {
|
|
33
|
-
const arrow = m.changePct >= 0 ? '\x1b[32m▲\x1b[39m' : '\x1b[31m▼\x1b[39m';
|
|
34
|
-
const chg = m.changePct >= 0 ? `\x1b[32m+${m.changePct}%\x1b[39m` : `\x1b[31m${m.changePct}%\x1b[39m`;
|
|
35
|
-
const price = `$${m.price.toFixed(2)}`;
|
|
36
|
-
console.log(` ${arrow} ${m.symbol.padEnd(5)} ${price.padStart(10)} ${chg.padStart(16)} ${m.name}`);
|
|
37
|
-
}
|
|
38
|
-
console.log();
|
|
39
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.milestonesCommand = milestonesCommand;
|
|
4
|
-
const client_js_1 = require("../client.js");
|
|
5
|
-
const utils_js_1 = require("../utils.js");
|
|
6
|
-
const KALSHI_API_BASE = 'https://api.elections.kalshi.com/trade-api/v2';
|
|
7
|
-
async function milestonesCommand(opts) {
|
|
8
|
-
const hours = parseInt(opts.hours || '168');
|
|
9
|
-
const now = new Date();
|
|
10
|
-
const cutoff = new Date(now.getTime() + hours * 3600000);
|
|
11
|
-
// Fetch milestones (public endpoint)
|
|
12
|
-
const url = `${KALSHI_API_BASE}/milestones?limit=200&minimum_start_date=${now.toISOString()}` +
|
|
13
|
-
(opts.category ? `&category=${opts.category}` : '');
|
|
14
|
-
const res = await fetch(url, { headers: { 'Accept': 'application/json' } });
|
|
15
|
-
if (!res.ok)
|
|
16
|
-
throw new Error(`Kalshi API ${res.status}`);
|
|
17
|
-
const data = await res.json();
|
|
18
|
-
let milestones = (data.milestones || []).filter((m) => new Date(m.start_date).getTime() <= cutoff.getTime());
|
|
19
|
-
// If thesis specified, filter to matching event tickers
|
|
20
|
-
if (opts.thesis) {
|
|
21
|
-
const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
|
|
22
|
-
const ctx = await client.getContext(opts.thesis);
|
|
23
|
-
const edgeEventTickers = new Set((ctx.edges || []).map((e) => e.eventTicker).filter(Boolean));
|
|
24
|
-
// Also match by related_event_tickers overlap
|
|
25
|
-
const edgeSeriesTickers = new Set((ctx.edges || []).map((e) => e.seriesTicker).filter(Boolean));
|
|
26
|
-
milestones = milestones.filter((m) => {
|
|
27
|
-
const related = m.related_event_tickers || m.primary_event_tickers || [];
|
|
28
|
-
return related.some((t) => edgeEventTickers.has(t) || edgeSeriesTickers.has(t.split('-')[0]));
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
if (opts.json) {
|
|
32
|
-
console.log(JSON.stringify(milestones, null, 2));
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (milestones.length === 0) {
|
|
36
|
-
console.log(`${utils_js_1.c.dim}No milestones in the next ${hours} hours.${utils_js_1.c.reset}`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// Sort by date
|
|
40
|
-
milestones.sort((a, b) => new Date(a.start_date).getTime() - new Date(b.start_date).getTime());
|
|
41
|
-
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Upcoming Milestones (next ${hours}h)${utils_js_1.c.reset}`);
|
|
42
|
-
console.log(`${utils_js_1.c.dim}${'─'.repeat(80)}${utils_js_1.c.reset}`);
|
|
43
|
-
for (const m of milestones) {
|
|
44
|
-
const date = new Date(m.start_date);
|
|
45
|
-
const hoursUntil = Math.round((date.getTime() - now.getTime()) / 3600000);
|
|
46
|
-
const timeStr = hoursUntil <= 24
|
|
47
|
-
? `${utils_js_1.c.bold}${hoursUntil}h${utils_js_1.c.reset}`
|
|
48
|
-
: `${utils_js_1.c.dim}${Math.round(hoursUntil / 24)}d${utils_js_1.c.reset}`;
|
|
49
|
-
const cat = `${utils_js_1.c.dim}[${m.category}]${utils_js_1.c.reset}`;
|
|
50
|
-
const tickers = (m.related_event_tickers || []).slice(0, 3).join(', ');
|
|
51
|
-
console.log(` ${timeStr.padEnd(12)} ${cat.padEnd(25)} ${m.title}`);
|
|
52
|
-
if (tickers)
|
|
53
|
-
console.log(` ${' '.repeat(10)} ${utils_js_1.c.dim}${tickers}${utils_js_1.c.reset}`);
|
|
54
|
-
}
|
|
55
|
-
console.log(`\n${utils_js_1.c.dim}${milestones.length} milestone(s)${utils_js_1.c.reset}`);
|
|
56
|
-
}
|