@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.
Files changed (180) hide show
  1. package/dist/101.index.js +1 -0
  2. package/dist/12.index.js +1 -0
  3. package/dist/160.index.js +1 -0
  4. package/dist/174.index.js +1 -0
  5. package/dist/278.index.js +6 -0
  6. package/dist/582.index.js +1 -0
  7. package/dist/641.index.js +324 -0
  8. package/dist/669.index.js +1 -0
  9. package/dist/722.index.js +1 -0
  10. package/dist/788.index.js +1 -0
  11. package/dist/816.index.js +12 -0
  12. package/dist/830.index.js +1 -0
  13. package/dist/921.index.js +1 -0
  14. package/dist/index.js +1 -813
  15. package/package.json +6 -2
  16. package/dist/cache.d.ts +0 -6
  17. package/dist/cache.js +0 -31
  18. package/dist/cache.test.d.ts +0 -1
  19. package/dist/cache.test.js +0 -73
  20. package/dist/client.d.ts +0 -56
  21. package/dist/client.js +0 -205
  22. package/dist/client.test.d.ts +0 -1
  23. package/dist/client.test.js +0 -89
  24. package/dist/commands/agent.d.ts +0 -20
  25. package/dist/commands/agent.js +0 -4119
  26. package/dist/commands/announcements.d.ts +0 -3
  27. package/dist/commands/announcements.js +0 -28
  28. package/dist/commands/augment.d.ts +0 -12
  29. package/dist/commands/augment.js +0 -56
  30. package/dist/commands/balance.d.ts +0 -3
  31. package/dist/commands/balance.js +0 -17
  32. package/dist/commands/book.d.ts +0 -17
  33. package/dist/commands/book.js +0 -220
  34. package/dist/commands/cancel.d.ts +0 -5
  35. package/dist/commands/cancel.js +0 -41
  36. package/dist/commands/context.d.ts +0 -6
  37. package/dist/commands/context.js +0 -208
  38. package/dist/commands/create.d.ts +0 -7
  39. package/dist/commands/create.js +0 -42
  40. package/dist/commands/dashboard.d.ts +0 -14
  41. package/dist/commands/dashboard.js +0 -215
  42. package/dist/commands/delta.d.ts +0 -16
  43. package/dist/commands/delta.js +0 -115
  44. package/dist/commands/edges.d.ts +0 -26
  45. package/dist/commands/edges.js +0 -237
  46. package/dist/commands/evaluate.d.ts +0 -4
  47. package/dist/commands/evaluate.js +0 -30
  48. package/dist/commands/explore.d.ts +0 -14
  49. package/dist/commands/explore.js +0 -115
  50. package/dist/commands/feed.d.ts +0 -13
  51. package/dist/commands/feed.js +0 -73
  52. package/dist/commands/fills.d.ts +0 -4
  53. package/dist/commands/fills.js +0 -29
  54. package/dist/commands/forecast.d.ts +0 -4
  55. package/dist/commands/forecast.js +0 -53
  56. package/dist/commands/get.d.ts +0 -5
  57. package/dist/commands/get.js +0 -98
  58. package/dist/commands/heartbeat.d.ts +0 -20
  59. package/dist/commands/heartbeat.js +0 -73
  60. package/dist/commands/history.d.ts +0 -3
  61. package/dist/commands/history.js +0 -38
  62. package/dist/commands/liquidity.d.ts +0 -14
  63. package/dist/commands/liquidity.js +0 -378
  64. package/dist/commands/list.d.ts +0 -5
  65. package/dist/commands/list.js +0 -38
  66. package/dist/commands/login.d.ts +0 -9
  67. package/dist/commands/login.js +0 -98
  68. package/dist/commands/markets.d.ts +0 -10
  69. package/dist/commands/markets.js +0 -39
  70. package/dist/commands/milestones.d.ts +0 -8
  71. package/dist/commands/milestones.js +0 -56
  72. package/dist/commands/orders.d.ts +0 -4
  73. package/dist/commands/orders.js +0 -28
  74. package/dist/commands/performance.d.ts +0 -11
  75. package/dist/commands/performance.js +0 -250
  76. package/dist/commands/positions.d.ts +0 -19
  77. package/dist/commands/positions.js +0 -294
  78. package/dist/commands/prompt.d.ts +0 -13
  79. package/dist/commands/prompt.js +0 -35
  80. package/dist/commands/publish.d.ts +0 -15
  81. package/dist/commands/publish.js +0 -39
  82. package/dist/commands/query.d.ts +0 -15
  83. package/dist/commands/query.js +0 -132
  84. package/dist/commands/rfq.d.ts +0 -5
  85. package/dist/commands/rfq.js +0 -35
  86. package/dist/commands/scan.d.ts +0 -11
  87. package/dist/commands/scan.js +0 -230
  88. package/dist/commands/schedule.d.ts +0 -3
  89. package/dist/commands/schedule.js +0 -38
  90. package/dist/commands/settlements.d.ts +0 -6
  91. package/dist/commands/settlements.js +0 -50
  92. package/dist/commands/setup.d.ts +0 -24
  93. package/dist/commands/setup.js +0 -700
  94. package/dist/commands/signal.d.ts +0 -6
  95. package/dist/commands/signal.js +0 -32
  96. package/dist/commands/strategies.d.ts +0 -11
  97. package/dist/commands/strategies.js +0 -130
  98. package/dist/commands/telegram.d.ts +0 -15
  99. package/dist/commands/telegram.js +0 -125
  100. package/dist/commands/trade.d.ts +0 -12
  101. package/dist/commands/trade.js +0 -112
  102. package/dist/commands/watch.d.ts +0 -19
  103. package/dist/commands/watch.js +0 -157
  104. package/dist/commands/whatif.d.ts +0 -17
  105. package/dist/commands/whatif.js +0 -209
  106. package/dist/commands/x.d.ts +0 -28
  107. package/dist/commands/x.js +0 -167
  108. package/dist/config.d.ts +0 -55
  109. package/dist/config.js +0 -139
  110. package/dist/config.test.d.ts +0 -1
  111. package/dist/config.test.js +0 -138
  112. package/dist/index.d.ts +0 -20
  113. package/dist/kalshi.d.ts +0 -144
  114. package/dist/kalshi.js +0 -498
  115. package/dist/polymarket.d.ts +0 -237
  116. package/dist/polymarket.js +0 -353
  117. package/dist/polymarket.test.d.ts +0 -1
  118. package/dist/polymarket.test.js +0 -424
  119. package/dist/share.d.ts +0 -4
  120. package/dist/share.js +0 -27
  121. package/dist/skills/loader.d.ts +0 -19
  122. package/dist/skills/loader.js +0 -86
  123. package/dist/telegram/agent-bridge.d.ts +0 -15
  124. package/dist/telegram/agent-bridge.js +0 -573
  125. package/dist/telegram/bot.d.ts +0 -10
  126. package/dist/telegram/bot.js +0 -297
  127. package/dist/telegram/commands.d.ts +0 -11
  128. package/dist/telegram/commands.js +0 -120
  129. package/dist/telegram/format.d.ts +0 -11
  130. package/dist/telegram/format.js +0 -51
  131. package/dist/telegram/format.test.d.ts +0 -1
  132. package/dist/telegram/format.test.js +0 -73
  133. package/dist/telegram/poller.d.ts +0 -6
  134. package/dist/telegram/poller.js +0 -32
  135. package/dist/topics.d.ts +0 -17
  136. package/dist/topics.js +0 -102
  137. package/dist/topics.test.d.ts +0 -1
  138. package/dist/topics.test.js +0 -131
  139. package/dist/tui/border.d.ts +0 -33
  140. package/dist/tui/border.js +0 -87
  141. package/dist/tui/chart.d.ts +0 -19
  142. package/dist/tui/chart.js +0 -117
  143. package/dist/tui/dashboard.d.ts +0 -9
  144. package/dist/tui/dashboard.js +0 -814
  145. package/dist/tui/layout.d.ts +0 -16
  146. package/dist/tui/layout.js +0 -41
  147. package/dist/tui/screen.d.ts +0 -33
  148. package/dist/tui/screen.js +0 -102
  149. package/dist/tui/state.d.ts +0 -40
  150. package/dist/tui/state.js +0 -36
  151. package/dist/tui/widgets/commandbar.d.ts +0 -8
  152. package/dist/tui/widgets/commandbar.js +0 -82
  153. package/dist/tui/widgets/detail.d.ts +0 -9
  154. package/dist/tui/widgets/detail.js +0 -151
  155. package/dist/tui/widgets/edges.d.ts +0 -4
  156. package/dist/tui/widgets/edges.js +0 -34
  157. package/dist/tui/widgets/liquidity.d.ts +0 -9
  158. package/dist/tui/widgets/liquidity.js +0 -142
  159. package/dist/tui/widgets/orders.d.ts +0 -4
  160. package/dist/tui/widgets/orders.js +0 -37
  161. package/dist/tui/widgets/portfolio.d.ts +0 -4
  162. package/dist/tui/widgets/portfolio.js +0 -59
  163. package/dist/tui/widgets/signals.d.ts +0 -4
  164. package/dist/tui/widgets/signals.js +0 -31
  165. package/dist/tui/widgets/statusbar.d.ts +0 -8
  166. package/dist/tui/widgets/statusbar.js +0 -72
  167. package/dist/tui/widgets/thesis.d.ts +0 -4
  168. package/dist/tui/widgets/thesis.js +0 -66
  169. package/dist/tui/widgets/trade.d.ts +0 -9
  170. package/dist/tui/widgets/trade.js +0 -117
  171. package/dist/tui/widgets/upcoming.d.ts +0 -4
  172. package/dist/tui/widgets/upcoming.js +0 -41
  173. package/dist/tui/widgets/whatif.d.ts +0 -7
  174. package/dist/tui/widgets/whatif.js +0 -113
  175. package/dist/types/output.d.ts +0 -412
  176. package/dist/types/output.js +0 -9
  177. package/dist/utils.d.ts +0 -42
  178. package/dist/utils.js +0 -124
  179. package/dist/utils.test.d.ts +0 -1
  180. package/dist/utils.test.js +0 -111
@@ -1,814 +0,0 @@
1
- "use strict";
2
- /**
3
- * TUI Dashboard Engine — main loop
4
- *
5
- * Manages the render loop, data loading, keypress handling, and mode transitions.
6
- * This is NOT the commander entry point — see commands/dashboard.ts for that.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.loadAllData = loadAllData;
10
- exports.startDashboard = startDashboard;
11
- const screen_js_1 = require("./screen.js");
12
- const layout_js_1 = require("./layout.js");
13
- const state_js_1 = require("./state.js");
14
- const border_js_1 = require("./border.js");
15
- const cache_js_1 = require("../cache.js");
16
- const config_js_1 = require("../config.js");
17
- const client_js_1 = require("../client.js");
18
- const kalshi_js_1 = require("../kalshi.js");
19
- const topics_js_1 = require("../topics.js");
20
- const polymarket_js_1 = require("../polymarket.js");
21
- // Widget renderers
22
- const portfolio_js_1 = require("./widgets/portfolio.js");
23
- const thesis_js_1 = require("./widgets/thesis.js");
24
- const edges_js_1 = require("./widgets/edges.js");
25
- const upcoming_js_1 = require("./widgets/upcoming.js");
26
- const orders_js_1 = require("./widgets/orders.js");
27
- const signals_js_1 = require("./widgets/signals.js");
28
- const statusbar_js_1 = require("./widgets/statusbar.js");
29
- const commandbar_js_1 = require("./widgets/commandbar.js");
30
- const detail_js_1 = require("./widgets/detail.js");
31
- const liquidity_js_1 = require("./widgets/liquidity.js");
32
- const whatif_js_1 = require("./widgets/whatif.js");
33
- const trade_js_1 = require("./widgets/trade.js");
34
- // Refresh intervals (ms)
35
- const REFRESH_POSITIONS = 15_000;
36
- const REFRESH_THESES = 30_000;
37
- const REFRESH_ORDERS = 10_000;
38
- const REFRESH_BALANCE = 30_000;
39
- const REFRESH_FEED = 60_000;
40
- const REFRESH_CANDLES = 60_000;
41
- let sfClient = null;
42
- function getClient() {
43
- if (sfClient)
44
- return sfClient;
45
- try {
46
- const config = (0, config_js_1.loadConfig)();
47
- if (!config.apiKey)
48
- return null;
49
- sfClient = new client_js_1.SFClient(config.apiKey, config.apiUrl);
50
- return sfClient;
51
- }
52
- catch {
53
- return null;
54
- }
55
- }
56
- // ============================================================================
57
- // DATA LOADING
58
- // ============================================================================
59
- async function loadAllData(state) {
60
- const client = getClient();
61
- // Parallel fetch of primary data
62
- const [positionsResult, thesesResult, ordersResult, balanceResult] = await Promise.allSettled([
63
- (0, cache_js_1.cached)('positions', REFRESH_POSITIONS, () => (0, kalshi_js_1.getPositions)()),
64
- client ? (0, cache_js_1.cached)('theses', REFRESH_THESES, () => client.listTheses()) : Promise.resolve(null),
65
- (0, cache_js_1.cached)('orders', REFRESH_ORDERS, () => (0, kalshi_js_1.getOrders)({ status: 'resting' })),
66
- (0, cache_js_1.cached)('balance', REFRESH_BALANCE, () => (0, kalshi_js_1.getBalance)()),
67
- ]);
68
- // Positions
69
- if (positionsResult.status === 'fulfilled' && positionsResult.value) {
70
- const positions = positionsResult.value;
71
- // Enrich with live prices
72
- const priceResults = await Promise.allSettled(positions.map(p => (0, cache_js_1.cached)(`price:${p.ticker}`, 10_000, () => (0, kalshi_js_1.getMarketPrice)(p.ticker))));
73
- for (let i = 0; i < positions.length; i++) {
74
- const pr = priceResults[i];
75
- if (pr.status === 'fulfilled' && pr.value != null) {
76
- positions[i].current_value = pr.value;
77
- positions[i].unrealized_pnl = Math.round((pr.value - positions[i].average_price_paid) * positions[i].quantity);
78
- }
79
- }
80
- state.positions = positions;
81
- }
82
- // Polymarket positions (merge into state.positions with venue tag)
83
- const config = (0, config_js_1.loadConfig)();
84
- if (config.polymarketWalletAddress) {
85
- try {
86
- const polyPos = await (0, cache_js_1.cached)('poly-positions', REFRESH_POSITIONS, () => (0, polymarket_js_1.polymarketGetPositions)(config.polymarketWalletAddress));
87
- if (polyPos && Array.isArray(polyPos) && polyPos.length > 0) {
88
- for (const p of polyPos) {
89
- state.positions.push({
90
- ticker: (p.title || p.slug || p.asset || '').slice(0, 25),
91
- quantity: p.size || 0,
92
- average_price_paid: Math.round((p.avgPrice || 0) * 100),
93
- current_value: Math.round((p.curPrice || p.currentPrice || 0) * 100),
94
- unrealized_pnl: Math.round((p.cashPnl || 0) * 100),
95
- side: (p.outcome || 'yes').toLowerCase(),
96
- venue: 'polymarket',
97
- });
98
- }
99
- }
100
- }
101
- catch {
102
- // skip
103
- }
104
- }
105
- // Theses
106
- if (thesesResult.status === 'fulfilled' && thesesResult.value) {
107
- const raw = thesesResult.value;
108
- state.theses = raw.theses || raw || [];
109
- }
110
- // Orders
111
- if (ordersResult.status === 'fulfilled' && ordersResult.value) {
112
- state.orders = ordersResult.value.orders || [];
113
- }
114
- // Balance
115
- if (balanceResult.status === 'fulfilled' && balanceResult.value) {
116
- state.balance = balanceResult.value.balance ?? 0;
117
- }
118
- // Fetch contexts for each thesis (parallel)
119
- if (client && state.theses.length > 0) {
120
- const ctxResults = await Promise.allSettled(state.theses.map(t => (0, cache_js_1.cached)(`ctx:${t.id}`, REFRESH_THESES, () => client.getContext(t.id))));
121
- for (let i = 0; i < state.theses.length; i++) {
122
- const cr = ctxResults[i];
123
- if (cr.status === 'fulfilled' && cr.value) {
124
- state.contexts.set(state.theses[i].id, cr.value);
125
- }
126
- }
127
- }
128
- // Compute edges from all contexts
129
- computeEdges(state);
130
- // Fetch candlesticks for position tickers
131
- await loadCandles(state);
132
- // Fetch feed for signals
133
- if (client) {
134
- try {
135
- const feed = await (0, cache_js_1.cached)('feed', REFRESH_FEED, () => client.getFeed(48));
136
- const entries = feed.entries || feed.feed || feed;
137
- if (Array.isArray(entries)) {
138
- state.signals = entries;
139
- }
140
- }
141
- catch { /* ignore */ }
142
- }
143
- // Collect upcoming events from contexts
144
- collectEvents(state);
145
- // Check exchange status
146
- checkExchangeStatus(state);
147
- state.lastRefresh.all = Date.now();
148
- }
149
- function computeEdges(state) {
150
- const allEdges = [];
151
- for (const [, ctx] of state.contexts) {
152
- if (!ctx?.edges)
153
- continue;
154
- for (const e of ctx.edges) {
155
- allEdges.push(e);
156
- }
157
- }
158
- // Dedup by marketId, keep highest |edge|
159
- const edgeMap = new Map();
160
- for (const e of allEdges) {
161
- const key = e.marketId || e.ticker || e.market;
162
- const existing = edgeMap.get(key);
163
- if (!existing || Math.abs(e.edge ?? 0) > Math.abs(existing.edge ?? 0)) {
164
- edgeMap.set(key, e);
165
- }
166
- }
167
- // Filter out held positions, sort by |edge|
168
- const heldTickers = new Set(state.positions.map(p => p.ticker_symbol || p.ticker));
169
- state.edges = [...edgeMap.values()]
170
- .filter(e => !heldTickers.has(e.marketId || e.ticker))
171
- .sort((a, b) => Math.abs(b.edge ?? 0) - Math.abs(a.edge ?? 0));
172
- }
173
- async function loadCandles(state) {
174
- const tickers = state.positions.map(p => p.ticker).filter(Boolean);
175
- if (tickers.length === 0)
176
- return;
177
- try {
178
- const now = Math.floor(Date.now() / 1000);
179
- const sevenDaysAgo = now - 7 * 86400;
180
- const result = await (0, cache_js_1.cached)('candles', REFRESH_CANDLES, () => (0, kalshi_js_1.getBatchCandlesticks)({ tickers, startTs: sevenDaysAgo, endTs: now }));
181
- for (const item of result) {
182
- if (item.market_ticker && item.candlesticks) {
183
- // Parse candles to extract close price in cents
184
- const parsed = item.candlesticks.map((c) => {
185
- const bidClose = parseFloat(c.yes_bid?.close_dollars || '0');
186
- const askClose = parseFloat(c.yes_ask?.close_dollars || '0');
187
- const mid = bidClose > 0 && askClose > 0 ? (bidClose + askClose) / 2 : bidClose || askClose;
188
- const closeDollars = parseFloat(c.price?.close_dollars || '0') || mid;
189
- return {
190
- close: Math.round(closeDollars * 100), // cents
191
- date: c.end_period_ts ? new Date(c.end_period_ts * 1000).toISOString().slice(0, 10) : '',
192
- end_period_ts: c.end_period_ts,
193
- };
194
- }).filter((c) => c.close > 0);
195
- state.candleCache.set(item.market_ticker, parsed);
196
- }
197
- }
198
- }
199
- catch { /* ignore */ }
200
- }
201
- function collectEvents(state) {
202
- const events = [];
203
- for (const [, ctx] of state.contexts) {
204
- if (ctx?.milestones) {
205
- for (const m of ctx.milestones) {
206
- events.push(m);
207
- }
208
- }
209
- if (ctx?.upcomingEvents) {
210
- for (const e of ctx.upcomingEvents) {
211
- events.push(e);
212
- }
213
- }
214
- }
215
- // Sort by timestamp ascending (soonest first)
216
- events.sort((a, b) => {
217
- const ta = new Date(a.timestamp || a.date || a.time || 0).getTime();
218
- const tb = new Date(b.timestamp || b.date || b.time || 0).getTime();
219
- return ta - tb;
220
- });
221
- state.events = events.filter(e => {
222
- const t = new Date(e.timestamp || e.date || e.time || 0).getTime();
223
- return t > Date.now();
224
- });
225
- }
226
- function checkExchangeStatus(state) {
227
- // Kalshi exchange hours: Mon-Fri, no federal holidays
228
- // Simplified heuristic
229
- const now = new Date();
230
- const day = now.getUTCDay();
231
- state.exchangeOpen = day >= 1 && day <= 5;
232
- }
233
- // ============================================================================
234
- // RENDERING
235
- // ============================================================================
236
- function renderFrame(screen, state) {
237
- // Clear back buffer (it's already cleared by ScreenBuffer)
238
- // Status bar (row 0)
239
- (0, statusbar_js_1.renderStatusBar)(screen, 0, state);
240
- // Command bar (last row)
241
- (0, commandbar_js_1.renderCommandBar)(screen, screen.rows - 1, state);
242
- // Mode-specific content
243
- switch (state.mode) {
244
- case 'overview':
245
- renderOverview(screen, state);
246
- break;
247
- case 'detail':
248
- (0, detail_js_1.renderDetail)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
249
- break;
250
- case 'liquidity':
251
- (0, liquidity_js_1.renderLiquidity)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
252
- break;
253
- case 'whatif':
254
- (0, whatif_js_1.renderWhatif)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
255
- break;
256
- case 'trade':
257
- // Render overview or detail behind the trade overlay
258
- if (state.prevMode === 'detail') {
259
- (0, detail_js_1.renderDetail)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
260
- }
261
- else {
262
- renderOverview(screen, state);
263
- }
264
- (0, trade_js_1.renderTrade)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
265
- break;
266
- }
267
- screen.flush();
268
- }
269
- function renderOverview(screen, state) {
270
- const isNarrow = screen.cols < 80;
271
- const regions = isNarrow
272
- ? (0, layout_js_1.narrowLayout)(screen.cols, screen.rows)
273
- : (0, layout_js_1.overviewLayout)(screen.cols, screen.rows);
274
- // Render vertical divider for wide layout
275
- if (!isNarrow) {
276
- const leftWidth = Math.floor(screen.cols * 0.55);
277
- for (let r = 1; r < screen.rows - 1; r++) {
278
- screen.write(r, leftWidth, '│', border_js_1.CLR.borderDim);
279
- }
280
- }
281
- for (const region of regions) {
282
- switch (region.name) {
283
- case 'positions':
284
- (0, portfolio_js_1.renderPortfolio)(screen, region, state);
285
- break;
286
- case 'thesis':
287
- (0, thesis_js_1.renderThesis)(screen, region, state);
288
- break;
289
- case 'edges':
290
- (0, edges_js_1.renderEdges)(screen, region, state);
291
- break;
292
- case 'upcoming':
293
- (0, upcoming_js_1.renderUpcoming)(screen, region, state);
294
- break;
295
- case 'orders':
296
- (0, orders_js_1.renderOrders)(screen, region, state);
297
- break;
298
- case 'signals':
299
- (0, signals_js_1.renderSignals)(screen, region, state);
300
- break;
301
- }
302
- }
303
- }
304
- // ============================================================================
305
- // KEYPRESS HANDLING
306
- // ============================================================================
307
- function handleKeypress(state, key, screen, scheduleRender, cleanup) {
308
- const ch = key.toString('utf-8');
309
- const isEsc = key[0] === 0x1b && key.length === 1;
310
- const isUp = key[0] === 0x1b && key[1] === 0x5b && key[2] === 0x41;
311
- const isDown = key[0] === 0x1b && key[1] === 0x5b && key[2] === 0x42;
312
- const isEnter = ch === '\r' || ch === '\n';
313
- const isCtrlC = key[0] === 0x03;
314
- if (isCtrlC || (ch === 'q' && state.mode !== 'trade')) {
315
- cleanup();
316
- return;
317
- }
318
- state.error = null;
319
- switch (state.mode) {
320
- case 'overview':
321
- handleOverviewKey(ch, isUp, isDown, isEnter, state, screen, scheduleRender, cleanup);
322
- break;
323
- case 'detail':
324
- handleDetailKey(ch, isEsc, state, screen, scheduleRender);
325
- break;
326
- case 'liquidity':
327
- handleLiquidityKey(ch, isUp, isDown, isEsc, state, screen, scheduleRender);
328
- break;
329
- case 'whatif':
330
- handleWhatifKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender);
331
- break;
332
- case 'trade':
333
- handleTradeKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender);
334
- break;
335
- }
336
- }
337
- function handleOverviewKey(ch, isUp, isDown, isEnter, state, screen, scheduleRender, cleanup) {
338
- const list = state.focusArea === 'positions' ? state.positions : state.edges;
339
- const maxIdx = Math.max(0, list.length - 1);
340
- if (ch === 'j' || isDown) {
341
- state.selectedIndex = Math.min(state.selectedIndex + 1, maxIdx);
342
- scheduleRender();
343
- }
344
- else if (ch === 'k' || isUp) {
345
- state.selectedIndex = Math.max(state.selectedIndex - 1, 0);
346
- scheduleRender();
347
- }
348
- else if (ch === '\t') {
349
- state.focusArea = state.focusArea === 'positions' ? 'edges' : 'positions';
350
- state.selectedIndex = 0;
351
- scheduleRender();
352
- }
353
- else if (isEnter) {
354
- if (state.focusArea === 'positions' && state.positions.length > 0) {
355
- const pos = state.positions[state.selectedIndex];
356
- state.detailTicker = pos?.ticker || null;
357
- state.prevMode = 'overview';
358
- state.mode = 'detail';
359
- scheduleRender();
360
- }
361
- }
362
- else if (ch === 'l') {
363
- state.prevMode = 'overview';
364
- state.mode = 'liquidity';
365
- state.liquiditySelectedIndex = 0;
366
- loadLiquidityData(state).then(scheduleRender);
367
- }
368
- else if (ch === 'w') {
369
- if (state.theses.length > 0) {
370
- state.prevMode = 'overview';
371
- state.mode = 'whatif';
372
- state.whatifThesisId = state.theses[0].id;
373
- loadWhatifData(state).then(scheduleRender);
374
- }
375
- }
376
- else if (ch === 'b') {
377
- openTrade(state, 'buy');
378
- scheduleRender();
379
- }
380
- else if (ch === 's') {
381
- openTrade(state, 'sell');
382
- scheduleRender();
383
- }
384
- else if (ch === 'e') {
385
- triggerEvaluate(state).then(scheduleRender);
386
- }
387
- else if (ch === 'r') {
388
- (0, cache_js_1.invalidateAll)();
389
- loadAllData(state).then(scheduleRender);
390
- }
391
- }
392
- function handleDetailKey(ch, isEsc, state, screen, scheduleRender) {
393
- if (isEsc) {
394
- state.mode = 'overview';
395
- scheduleRender();
396
- }
397
- else if (ch === 'b') {
398
- openTrade(state, 'buy');
399
- scheduleRender();
400
- }
401
- else if (ch === 's') {
402
- openTrade(state, 'sell');
403
- scheduleRender();
404
- }
405
- else if (ch === 'w') {
406
- if (state.theses.length > 0) {
407
- state.prevMode = 'detail';
408
- state.mode = 'whatif';
409
- state.whatifThesisId = state.theses[0].id;
410
- loadWhatifData(state).then(scheduleRender);
411
- }
412
- }
413
- }
414
- function handleLiquidityKey(ch, isUp, isDown, isEsc, state, screen, scheduleRender) {
415
- if (isEsc) {
416
- state.mode = state.prevMode;
417
- scheduleRender();
418
- }
419
- else if (ch === 'j' || isDown) {
420
- state.liquiditySelectedIndex++;
421
- scheduleRender();
422
- }
423
- else if (ch === 'k' || isUp) {
424
- state.liquiditySelectedIndex = Math.max(0, state.liquiditySelectedIndex - 1);
425
- scheduleRender();
426
- }
427
- else if (ch === '\t') {
428
- // Cycle to next topic
429
- const topics = Object.keys(topics_js_1.TOPIC_SERIES);
430
- const idx = topics.indexOf(state.liquidityTopic);
431
- state.liquidityTopic = topics[(idx + 1) % topics.length];
432
- state.liquiditySelectedIndex = 0;
433
- loadLiquidityData(state).then(scheduleRender);
434
- }
435
- else if (ch === 'b') {
436
- openTradeFromLiquidity(state);
437
- scheduleRender();
438
- }
439
- else if (ch === 'r') {
440
- loadLiquidityData(state).then(scheduleRender);
441
- }
442
- }
443
- function handleWhatifKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender) {
444
- if (isEsc) {
445
- state.mode = state.prevMode;
446
- scheduleRender();
447
- }
448
- else if (ch === 'j' || isDown) {
449
- const max = (state.whatifResult?.scenarios?.length || 1) - 1;
450
- state.whatifScenarioIndex = Math.min(state.whatifScenarioIndex + 1, max);
451
- scheduleRender();
452
- }
453
- else if (ch === 'k' || isUp) {
454
- state.whatifScenarioIndex = Math.max(0, state.whatifScenarioIndex - 1);
455
- scheduleRender();
456
- }
457
- else if (isEnter) {
458
- // Apply scenario — could trigger re-evaluation
459
- scheduleRender();
460
- }
461
- }
462
- function handleTradeKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender) {
463
- if (isEsc) {
464
- state.mode = state.prevMode;
465
- state.tradeParams = null;
466
- state.tradeCountdown = -1;
467
- scheduleRender();
468
- return;
469
- }
470
- if (!state.tradeParams)
471
- return;
472
- if (ch === '\t') {
473
- state.tradeField = state.tradeField === 'qty' ? 'price' : 'qty';
474
- scheduleRender();
475
- }
476
- else if (isUp) {
477
- if (state.tradeField === 'qty') {
478
- state.tradeParams.qty = Math.min(state.tradeParams.qty + 1, 9999);
479
- }
480
- else {
481
- state.tradeParams.price = Math.min(state.tradeParams.price + 1, 99);
482
- }
483
- scheduleRender();
484
- }
485
- else if (isDown) {
486
- if (state.tradeField === 'qty') {
487
- state.tradeParams.qty = Math.max(state.tradeParams.qty - 1, 1);
488
- }
489
- else {
490
- state.tradeParams.price = Math.max(state.tradeParams.price - 1, 1);
491
- }
492
- scheduleRender();
493
- }
494
- else if (isEnter) {
495
- if (state.tradeCountdown < 0) {
496
- // Start countdown
497
- state.tradeCountdown = 3;
498
- scheduleRender();
499
- startCountdown(state, screen, scheduleRender);
500
- }
501
- }
502
- }
503
- function startCountdown(state, screen, scheduleRender) {
504
- const tick = () => {
505
- if (state.tradeCountdown <= 0 || state.mode !== 'trade')
506
- return;
507
- state.tradeCountdown--;
508
- scheduleRender();
509
- if (state.tradeCountdown === 0) {
510
- // Execute trade
511
- executeTrade(state, scheduleRender);
512
- }
513
- else {
514
- setTimeout(tick, 1000);
515
- }
516
- };
517
- setTimeout(tick, 1000);
518
- }
519
- // ============================================================================
520
- // TRADE HELPERS
521
- // ============================================================================
522
- function openTrade(state, action) {
523
- let ticker = '';
524
- if (state.focusArea === 'positions' && state.positions.length > 0) {
525
- const pos = state.positions[state.selectedIndex];
526
- ticker = pos?.ticker_symbol || pos?.ticker || '';
527
- }
528
- else if (state.focusArea === 'edges' && state.edges.length > 0) {
529
- const edge = state.edges[state.selectedIndex];
530
- ticker = edge?.ticker || edge?.marketId || '';
531
- }
532
- else if (state.detailTicker) {
533
- ticker = state.detailTicker;
534
- }
535
- if (!ticker) {
536
- state.error = 'No market selected';
537
- return;
538
- }
539
- state.prevMode = state.mode;
540
- state.mode = 'trade';
541
- state.tradeParams = {
542
- ticker,
543
- side: 'yes',
544
- action,
545
- qty: 10,
546
- price: 50,
547
- };
548
- state.tradeCountdown = -1;
549
- state.tradeField = 'qty';
550
- }
551
- function openTradeFromLiquidity(state) {
552
- const markets = state.liquidityData.get(state.liquidityTopic) || [];
553
- if (markets.length === 0)
554
- return;
555
- const sel = markets[Math.min(state.liquiditySelectedIndex, markets.length - 1)];
556
- if (!sel)
557
- return;
558
- state.prevMode = state.mode;
559
- state.mode = 'trade';
560
- state.tradeParams = {
561
- ticker: sel.ticker || '',
562
- side: 'yes',
563
- action: 'buy',
564
- qty: 10,
565
- price: sel.bestAsk ?? sel.yes_ask ?? 50,
566
- };
567
- state.tradeCountdown = -1;
568
- state.tradeField = 'qty';
569
- }
570
- async function executeTrade(state, scheduleRender) {
571
- if (!state.tradeParams)
572
- return;
573
- try {
574
- const config = (0, config_js_1.loadConfig)();
575
- if (!config.tradingEnabled) {
576
- state.error = 'Trading disabled. Run: sf setup --enable-trading';
577
- state.tradeCountdown = -1;
578
- scheduleRender();
579
- return;
580
- }
581
- await (0, kalshi_js_1.createOrder)({
582
- ticker: state.tradeParams.ticker,
583
- side: state.tradeParams.side,
584
- action: state.tradeParams.action,
585
- type: 'limit',
586
- count: state.tradeParams.qty,
587
- yes_price: state.tradeParams.price,
588
- });
589
- state.error = null;
590
- state.mode = state.prevMode;
591
- state.tradeParams = null;
592
- state.tradeCountdown = -1;
593
- // Refresh orders
594
- (0, cache_js_1.invalidateAll)();
595
- await loadAllData(state);
596
- }
597
- catch (err) {
598
- state.error = err instanceof Error ? err.message.slice(0, 40) : 'Trade failed';
599
- state.tradeCountdown = -1;
600
- }
601
- scheduleRender();
602
- }
603
- // ============================================================================
604
- // SECONDARY DATA LOADING
605
- // ============================================================================
606
- async function loadLiquidityData(state) {
607
- const topic = state.liquidityTopic;
608
- const seriesList = topics_js_1.TOPIC_SERIES[topic];
609
- if (!seriesList)
610
- return;
611
- try {
612
- // Phase 1: Fetch market lists in parallel (fast — just metadata)
613
- const seriesResults = await Promise.allSettled(seriesList.map(series => (0, cache_js_1.cached)(`liq:${series}`, 30_000, async () => {
614
- const url = `https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${series}&status=open&limit=200`;
615
- const res = await fetch(url, { headers: { Accept: 'application/json' } });
616
- if (!res.ok)
617
- return [];
618
- const data = await res.json();
619
- return data.markets || [];
620
- })));
621
- const allMarkets = [];
622
- for (const r of seriesResults) {
623
- if (r.status === 'fulfilled' && Array.isArray(r.value)) {
624
- allMarkets.push(...r.value);
625
- }
626
- }
627
- // Show markets immediately (before orderbooks load)
628
- state.liquidityData.set(topic, [...allMarkets]);
629
- // Phase 2: Fetch orderbooks in parallel batches of 8
630
- const BATCH = 8;
631
- for (let i = 0; i < allMarkets.length; i += BATCH) {
632
- const batch = allMarkets.slice(i, i + BATCH);
633
- const obResults = await Promise.allSettled(batch.map(mkt => (0, cache_js_1.cached)(`ob:${mkt.ticker}`, 30_000, () => (0, kalshi_js_1.getPublicOrderbook)(mkt.ticker))
634
- .then(ob => ({ mkt, ob }))));
635
- for (const r of obResults) {
636
- if (r.status !== 'fulfilled' || !r.value.ob)
637
- continue;
638
- const { mkt, ob } = r.value;
639
- const yes = (ob.yes_dollars || []).map((l) => ({
640
- price: Math.round(parseFloat(l[0]) * 100),
641
- qty: parseFloat(l[1]),
642
- })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
643
- const no = (ob.no_dollars || []).map((l) => ({
644
- price: Math.round(parseFloat(l[0]) * 100),
645
- qty: parseFloat(l[1]),
646
- })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
647
- mkt.bestBid = yes.length > 0 ? yes[0].price : 0;
648
- mkt.bestAsk = no.length > 0 ? (100 - no[0].price) : 100;
649
- mkt.spread = mkt.bestAsk - mkt.bestBid;
650
- mkt.totalDepth = yes.slice(0, 3).reduce((s, l) => s + l.qty, 0)
651
- + no.slice(0, 3).reduce((s, l) => s + l.qty, 0);
652
- }
653
- // Progressive update: refresh display after each batch
654
- state.liquidityData.set(topic, [...allMarkets]);
655
- }
656
- }
657
- catch (err) {
658
- state.error = 'Failed to load liquidity data';
659
- }
660
- }
661
- async function loadWhatifData(state) {
662
- const client = getClient();
663
- if (!client || !state.whatifThesisId)
664
- return;
665
- try {
666
- const ctx = state.contexts.get(state.whatifThesisId);
667
- if (!ctx)
668
- return;
669
- // Build scenario list from causal tree nodes
670
- const nodes = ctx.causalTree?.nodes || ctx.nodes || [];
671
- const scenarios = nodes.slice(0, 10).map((n) => ({
672
- name: n.name || n.title || n.id,
673
- nodeId: n.id,
674
- description: `Set ${n.name || n.id} to low probability`,
675
- }));
676
- state.whatifResult = {
677
- scenarios,
678
- before: {
679
- confidence: state.theses[0]?.confidence,
680
- edges: state.edges.slice(0, 5),
681
- },
682
- after: null,
683
- };
684
- }
685
- catch {
686
- state.error = 'Failed to load what-if data';
687
- }
688
- }
689
- async function triggerEvaluate(state) {
690
- const client = getClient();
691
- if (!client || state.theses.length === 0)
692
- return;
693
- try {
694
- state.error = null;
695
- const thesis = state.theses[0];
696
- await client.evaluate(thesis.id);
697
- // Refresh after evaluation
698
- (0, cache_js_1.invalidateAll)();
699
- await loadAllData(state);
700
- }
701
- catch (err) {
702
- state.error = err instanceof Error ? err.message.slice(0, 40) : 'Evaluation failed';
703
- }
704
- }
705
- // ============================================================================
706
- // MAIN LOOP
707
- // ============================================================================
708
- async function startDashboard() {
709
- const screen = new screen_js_1.ScreenBuffer();
710
- const state = (0, state_js_1.initialState)();
711
- // Enter alternate screen, hide cursor
712
- process.stdout.write('\x1b[?1049h');
713
- process.stdout.write('\x1b[?25l');
714
- // Enable raw mode
715
- if (process.stdin.isTTY) {
716
- process.stdin.setRawMode(true);
717
- }
718
- process.stdin.resume();
719
- let renderQueued = false;
720
- const scheduleRender = () => {
721
- if (renderQueued)
722
- return;
723
- renderQueued = true;
724
- setImmediate(() => {
725
- renderQueued = false;
726
- renderFrame(screen, state);
727
- });
728
- };
729
- // Cleanup function
730
- const intervals = [];
731
- let cleaned = false;
732
- const cleanup = () => {
733
- if (cleaned)
734
- return;
735
- cleaned = true;
736
- for (const iv of intervals)
737
- clearInterval(iv);
738
- // Restore terminal
739
- process.stdout.write('\x1b[?1049l'); // Exit alternate screen
740
- process.stdout.write('\x1b[?25h'); // Show cursor
741
- if (process.stdin.isTTY) {
742
- process.stdin.setRawMode(false);
743
- }
744
- process.stdin.pause();
745
- process.exit(0);
746
- };
747
- // Handle signals
748
- process.on('SIGINT', cleanup);
749
- process.on('SIGTERM', cleanup);
750
- // Load initial data
751
- try {
752
- await loadAllData(state);
753
- }
754
- catch (err) {
755
- state.error = err instanceof Error ? err.message.slice(0, 50) : 'Failed to load data';
756
- }
757
- // First render
758
- renderFrame(screen, state);
759
- // Set up refresh intervals
760
- intervals.push(setInterval(async () => {
761
- try {
762
- const positions = await (0, cache_js_1.cached)('positions', REFRESH_POSITIONS, () => (0, kalshi_js_1.getPositions)());
763
- if (positions) {
764
- const priceResults = await Promise.allSettled(positions.map(p => (0, cache_js_1.cached)(`price:${p.ticker}`, 10_000, () => (0, kalshi_js_1.getMarketPrice)(p.ticker))));
765
- for (let i = 0; i < positions.length; i++) {
766
- const pr = priceResults[i];
767
- if (pr.status === 'fulfilled' && pr.value != null) {
768
- positions[i].current_value = pr.value;
769
- }
770
- }
771
- state.positions = positions;
772
- computeEdges(state);
773
- }
774
- scheduleRender();
775
- }
776
- catch { /* ignore */ }
777
- }, REFRESH_POSITIONS));
778
- intervals.push(setInterval(async () => {
779
- try {
780
- const ordersResult = await (0, cache_js_1.cached)('orders', REFRESH_ORDERS, () => (0, kalshi_js_1.getOrders)({ status: 'resting' }));
781
- if (ordersResult) {
782
- state.orders = ordersResult.orders || [];
783
- }
784
- scheduleRender();
785
- }
786
- catch { /* ignore */ }
787
- }, REFRESH_ORDERS));
788
- intervals.push(setInterval(async () => {
789
- try {
790
- const balanceResult = await (0, cache_js_1.cached)('balance', REFRESH_BALANCE, () => (0, kalshi_js_1.getBalance)());
791
- if (balanceResult) {
792
- state.balance = balanceResult.balance ?? 0;
793
- }
794
- scheduleRender();
795
- }
796
- catch { /* ignore */ }
797
- }, REFRESH_BALANCE));
798
- intervals.push(setInterval(async () => {
799
- try {
800
- await loadCandles(state);
801
- scheduleRender();
802
- }
803
- catch { /* ignore */ }
804
- }, REFRESH_CANDLES));
805
- // Keypress handler
806
- process.stdin.on('data', (key) => {
807
- handleKeypress(state, key, screen, scheduleRender, cleanup);
808
- });
809
- // Resize handler
810
- process.stdout.on('resize', () => {
811
- screen.resize();
812
- scheduleRender();
813
- });
814
- }