@spfunctions/cli 1.7.39 → 2.0.3

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/12.index.js CHANGED
@@ -1,536 +1 @@
1
- "use strict";
2
- exports.id = 12;
3
- exports.ids = [12];
4
- exports.modules = {
5
-
6
- /***/ 6012:
7
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
8
-
9
- var __webpack_unused_export__;
10
-
11
- /**
12
- * Telegram Bot — main entry point
13
- *
14
- * Handles slash commands (zero LLM) and free text (via pi-agent-core).
15
- * Polls delta API for push notifications.
16
- */
17
- __webpack_unused_export__ = ({ value: true });
18
- exports.startBot = startBot;
19
- const grammy_1 = __webpack_require__(53278);
20
- const client_js_1 = __webpack_require__(19218);
21
- const config_js_1 = __webpack_require__(11627);
22
- const format_js_1 = __webpack_require__(76342);
23
- const commands_js_1 = __webpack_require__(77499);
24
- const poller_js_1 = __webpack_require__(55875);
25
- const sessions = new Map();
26
- function getSession(chatId) {
27
- if (!sessions.has(chatId)) {
28
- sessions.set(chatId, { thesisId: null, agentMessages: [] });
29
- }
30
- return sessions.get(chatId);
31
- }
32
- async function startBot(opts) {
33
- const config = (0, config_js_1.loadConfig)();
34
- const botToken = opts.token || config.telegramBotToken || process.env.TELEGRAM_BOT_TOKEN;
35
- if (!botToken) {
36
- console.error('No Telegram bot token. Use --token, set TELEGRAM_BOT_TOKEN, or add to ~/.sf/config.json');
37
- process.exit(1);
38
- }
39
- const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
40
- const bot = new grammy_1.Bot(botToken);
41
- const allowedChat = opts.chatId || null;
42
- const pollers = new Map();
43
- // Auth guard
44
- function isAllowed(chatId) {
45
- return !allowedChat || chatId === allowedChat;
46
- }
47
- // ── /start ──
48
- bot.command('start', async (ctx) => {
49
- if (!isAllowed(ctx.chat.id))
50
- return;
51
- const session = getSession(ctx.chat.id);
52
- // Auto-detect first active thesis
53
- try {
54
- const { theses } = await client.listTheses();
55
- const active = (theses || []).filter((t) => t.status === 'active');
56
- if (active.length > 0) {
57
- session.thesisId = active[0].id;
58
- await ctx.reply(`✅ Connected to SimpleFunctions\n\n` +
59
- `Active thesis: <b>${(active[0].rawThesis || '').slice(0, 60)}</b>\n` +
60
- `ID: <code>${active[0].id.slice(0, 8)}</code>\n\n` +
61
- `Commands:\n` +
62
- `/context — thesis snapshot\n` +
63
- `/positions — Kalshi positions\n` +
64
- `/edges — top edges\n` +
65
- `/balance — account balance\n` +
66
- `/orders — resting orders\n` +
67
- `/eval — trigger evaluation\n` +
68
- `/list — all theses\n` +
69
- `/switch — switch thesis\n\n` +
70
- `Or just type naturally.`, { parse_mode: 'HTML' });
71
- // Start polling for this chat
72
- if (pollers.has(ctx.chat.id))
73
- clearInterval(pollers.get(ctx.chat.id));
74
- pollers.set(ctx.chat.id, (0, poller_js_1.startPoller)(bot, ctx.chat.id, client, session.thesisId));
75
- }
76
- else {
77
- await ctx.reply('No active theses found. Create one with `sf create "your thesis"`');
78
- }
79
- }
80
- catch (err) {
81
- await ctx.reply(`❌ Connection failed: ${err.message}\nCheck SF_API_KEY in ~/.sf/config.json`);
82
- }
83
- });
84
- // ── /context ──
85
- bot.command('context', async (ctx) => {
86
- if (!isAllowed(ctx.chat.id))
87
- return;
88
- const session = getSession(ctx.chat.id);
89
- if (!session.thesisId) {
90
- await ctx.reply('No thesis selected. Use /start or /switch <id>');
91
- return;
92
- }
93
- try {
94
- const text = await (0, commands_js_1.handleContext)(client, session.thesisId);
95
- for (const chunk of (0, format_js_1.splitMessage)(text))
96
- await ctx.reply(chunk, { parse_mode: 'HTML' });
97
- }
98
- catch (err) {
99
- await ctx.reply(`❌ ${err.message}`);
100
- }
101
- });
102
- // ── /positions ──
103
- bot.command('positions', async (ctx) => {
104
- if (!isAllowed(ctx.chat.id))
105
- return;
106
- try {
107
- const text = await (0, commands_js_1.handlePositions)();
108
- for (const chunk of (0, format_js_1.splitMessage)(text))
109
- await ctx.reply(chunk, { parse_mode: 'HTML' });
110
- }
111
- catch (err) {
112
- await ctx.reply(`❌ ${err.message}`);
113
- }
114
- });
115
- // ── /edges ──
116
- bot.command('edges', async (ctx) => {
117
- if (!isAllowed(ctx.chat.id))
118
- return;
119
- try {
120
- const text = await (0, commands_js_1.handleEdges)(client);
121
- for (const chunk of (0, format_js_1.splitMessage)(text))
122
- await ctx.reply(chunk, { parse_mode: 'HTML' });
123
- }
124
- catch (err) {
125
- await ctx.reply(`❌ ${err.message}`);
126
- }
127
- });
128
- // ── /balance ──
129
- bot.command('balance', async (ctx) => {
130
- if (!isAllowed(ctx.chat.id))
131
- return;
132
- try {
133
- const text = await (0, commands_js_1.handleBalance)();
134
- await ctx.reply(text, { parse_mode: 'HTML' });
135
- }
136
- catch (err) {
137
- await ctx.reply(`❌ ${err.message}`);
138
- }
139
- });
140
- // ── /orders ──
141
- bot.command('orders', async (ctx) => {
142
- if (!isAllowed(ctx.chat.id))
143
- return;
144
- try {
145
- const text = await (0, commands_js_1.handleOrders)();
146
- for (const chunk of (0, format_js_1.splitMessage)(text))
147
- await ctx.reply(chunk, { parse_mode: 'HTML' });
148
- }
149
- catch (err) {
150
- await ctx.reply(`❌ ${err.message}`);
151
- }
152
- });
153
- // ── /eval ──
154
- bot.command('eval', async (ctx) => {
155
- if (!isAllowed(ctx.chat.id))
156
- return;
157
- const session = getSession(ctx.chat.id);
158
- if (!session.thesisId) {
159
- await ctx.reply('No thesis selected.');
160
- return;
161
- }
162
- try {
163
- const text = await (0, commands_js_1.handleEval)(client, session.thesisId);
164
- await ctx.reply(text);
165
- }
166
- catch (err) {
167
- await ctx.reply(`❌ ${err.message}`);
168
- }
169
- });
170
- // ── /list ──
171
- bot.command('list', async (ctx) => {
172
- if (!isAllowed(ctx.chat.id))
173
- return;
174
- try {
175
- const text = await (0, commands_js_1.handleList)(client);
176
- for (const chunk of (0, format_js_1.splitMessage)(text))
177
- await ctx.reply(chunk, { parse_mode: 'HTML' });
178
- }
179
- catch (err) {
180
- await ctx.reply(`❌ ${err.message}`);
181
- }
182
- });
183
- // ── /switch <id> ──
184
- bot.command('switch', async (ctx) => {
185
- if (!isAllowed(ctx.chat.id))
186
- return;
187
- const session = getSession(ctx.chat.id);
188
- const newId = ctx.match?.trim();
189
- if (!newId) {
190
- await ctx.reply('Usage: /switch <thesis_id>');
191
- return;
192
- }
193
- // Find matching thesis (prefix match)
194
- try {
195
- const { theses } = await client.listTheses();
196
- const match = (theses || []).find((t) => t.id.startsWith(newId));
197
- if (match) {
198
- session.thesisId = match.id;
199
- session.agentMessages = []; // reset conversation
200
- // Restart poller
201
- if (pollers.has(ctx.chat.id))
202
- clearInterval(pollers.get(ctx.chat.id));
203
- pollers.set(ctx.chat.id, (0, poller_js_1.startPoller)(bot, ctx.chat.id, client, session.thesisId));
204
- await ctx.reply(`Switched to: <code>${match.id.slice(0, 8)}</code> — ${(match.rawThesis || '').slice(0, 60)}`, { parse_mode: 'HTML' });
205
- }
206
- else {
207
- await ctx.reply(`No thesis found matching "${newId}". Use /list to see all.`);
208
- }
209
- }
210
- catch (err) {
211
- await ctx.reply(`❌ ${err.message}`);
212
- }
213
- });
214
- // ── Free text → LLM agent ──
215
- bot.on('message:text', async (ctx) => {
216
- if (!isAllowed(ctx.chat.id))
217
- return;
218
- const session = getSession(ctx.chat.id);
219
- const userMessage = ctx.message.text;
220
- if (userMessage.startsWith('/'))
221
- return; // skip unhandled commands
222
- // Auto-detect thesis if not set
223
- if (!session.thesisId) {
224
- try {
225
- const { theses } = await client.listTheses();
226
- const active = (theses || []).filter((t) => t.status === 'active');
227
- if (active.length > 0) {
228
- session.thesisId = active[0].id;
229
- }
230
- else {
231
- await ctx.reply('No active theses. Create one with `sf create "your thesis"` then /start');
232
- return;
233
- }
234
- }
235
- catch {
236
- await ctx.reply('Could not connect. Use /start first.');
237
- return;
238
- }
239
- }
240
- try {
241
- // Show "typing" indicator
242
- await ctx.replyWithChatAction('typing');
243
- // Lazy-load agent module (heavy imports)
244
- const { runAgentMessage } = await __webpack_require__.e(/* import() */ 160).then(__webpack_require__.bind(__webpack_require__, 28160));
245
- // Timeout after 30 seconds
246
- const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Response timeout (30s)')), 30_000));
247
- const response = await Promise.race([
248
- runAgentMessage(client, session, userMessage),
249
- timeout,
250
- ]);
251
- if (!response || response.trim().length === 0) {
252
- await ctx.reply('No response generated. Try rephrasing or use a slash command.');
253
- }
254
- else {
255
- for (const chunk of (0, format_js_1.splitMessage)(response)) {
256
- // Use plain text if HTML parsing might fail
257
- try {
258
- await ctx.reply(chunk, { parse_mode: 'HTML' });
259
- }
260
- catch {
261
- await ctx.reply(chunk); // fallback to plain text
262
- }
263
- }
264
- }
265
- }
266
- catch (err) {
267
- console.error('[Telegram] Agent error:', err.message);
268
- await ctx.reply(`❌ ${err.message}`);
269
- }
270
- });
271
- // ── Callback queries (for inline keyboard confirmations) ──
272
- bot.on('callback_query:data', async (ctx) => {
273
- const data = ctx.callbackQuery.data;
274
- if (data.startsWith('order_confirm:')) {
275
- // TODO: execute pending order
276
- await ctx.answerCallbackQuery({ text: 'Order execution coming soon' });
277
- }
278
- else if (data === 'order_cancel') {
279
- await ctx.answerCallbackQuery({ text: 'Order cancelled' });
280
- await ctx.editMessageText('❌ Order cancelled.');
281
- }
282
- });
283
- // ── Start bot ──
284
- console.log('🤖 SimpleFunctions Telegram bot starting...');
285
- console.log(` SF API: ${config.apiKey ? '✓' : '✗'}`);
286
- console.log(` Kalshi: ${process.env.KALSHI_API_KEY_ID ? '✓' : '✗'}`);
287
- console.log(` OpenRouter: ${config.openrouterKey ? '✓' : '✗'}`);
288
- if (allowedChat)
289
- console.log(` Restricted to chat: ${allowedChat}`);
290
- console.log(' Press Ctrl+C to stop.\n');
291
- // Prevent crashes from killing the daemon
292
- process.on('uncaughtException', (err) => {
293
- console.error('[Telegram] Uncaught exception:', err.message);
294
- });
295
- process.on('unhandledRejection', (err) => {
296
- console.error('[Telegram] Unhandled rejection:', err?.message || err);
297
- });
298
- // Cleanup on exit
299
- process.on('SIGINT', () => {
300
- for (const timer of pollers.values())
301
- clearInterval(timer);
302
- bot.stop();
303
- process.exit(0);
304
- });
305
- await bot.start();
306
- }
307
-
308
-
309
- /***/ }),
310
-
311
- /***/ 77499:
312
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
313
-
314
-
315
- /**
316
- * Telegram slash command handlers — direct API calls, zero LLM cost
317
- */
318
- Object.defineProperty(exports, "__esModule", ({ value: true }));
319
- exports.handleContext = handleContext;
320
- exports.handlePositions = handlePositions;
321
- exports.handleEdges = handleEdges;
322
- exports.handleBalance = handleBalance;
323
- exports.handleOrders = handleOrders;
324
- exports.handleEval = handleEval;
325
- exports.handleList = handleList;
326
- const kalshi_js_1 = __webpack_require__(96139);
327
- const format_js_1 = __webpack_require__(76342);
328
- async function handleContext(client, thesisId) {
329
- const ctx = await client.getContext(thesisId);
330
- const conf = typeof ctx.confidence === 'number' ? Math.round(ctx.confidence * 100) : '?';
331
- const thesis = ctx.thesis || ctx.rawThesis || 'N/A';
332
- let text = `📋 <b>${(0, format_js_1.escapeHtml)(thesis.slice(0, 80))}</b>\n`;
333
- text += `Confidence: <b>${conf}%</b> | Status: ${ctx.status}\n\n`;
334
- const edges = (ctx.edges || []).slice(0, 8);
335
- if (edges.length > 0) {
336
- text += `<b>Top Edges:</b>\n<pre>`;
337
- for (const e of edges) {
338
- const name = (e.market || e.marketTitle || '???').slice(0, 35);
339
- text += `${name.padEnd(36)} ${String(e.edge || 0).padStart(3)}¢ ${e.direction || 'yes'}\n`;
340
- }
341
- text += `</pre>`;
342
- }
343
- return text;
344
- }
345
- async function handlePositions() {
346
- if (!(0, kalshi_js_1.isKalshiConfigured)())
347
- return '⚠️ Kalshi not configured. Run <code>sf setup --kalshi</code>';
348
- const positions = await (0, kalshi_js_1.getPositions)();
349
- if (!positions || positions.length === 0)
350
- return 'No open positions.';
351
- // Enrich with live prices
352
- let totalPnlCents = 0;
353
- const lines = [];
354
- for (const pos of positions) {
355
- const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
356
- const current = livePrice ?? pos.average_price_paid;
357
- const pnlCents = (current - pos.average_price_paid) * pos.quantity;
358
- totalPnlCents += pnlCents;
359
- const pnl = (0, format_js_1.fmtDollar)(pnlCents);
360
- lines.push(`${pos.ticker.slice(0, 28).padEnd(29)} ${String(pos.quantity).padStart(5)} ${String(pos.average_price_paid).padStart(3)}¢→${String(current).padStart(3)}¢ ${pnl}`);
361
- }
362
- let text = `📊 <b>Positions</b>\n<pre>`;
363
- text += lines.join('\n');
364
- text += `\n${'─'.repeat(50)}\nTotal P&L: ${(0, format_js_1.fmtDollar)(totalPnlCents)}`;
365
- text += `</pre>`;
366
- return text;
367
- }
368
- async function handleEdges(client) {
369
- const { theses } = await client.listTheses();
370
- const active = (theses || []).filter((t) => t.status === 'active');
371
- const results = await Promise.allSettled(active.map(async (t) => {
372
- const ctx = await client.getContext(t.id);
373
- return (ctx.edges || []).map((e) => ({ ...e, thesisId: t.id }));
374
- }));
375
- const allEdges = [];
376
- for (const r of results) {
377
- if (r.status === 'fulfilled')
378
- allEdges.push(...r.value);
379
- }
380
- allEdges.sort((a, b) => Math.abs(b.edge || 0) - Math.abs(a.edge || 0));
381
- const top = allEdges.slice(0, 10);
382
- if (top.length === 0)
383
- return 'No edges found.';
384
- let text = `📈 <b>Top Edges</b>\n<pre>`;
385
- for (const e of top) {
386
- const name = (e.market || '???').slice(0, 35);
387
- const liq = e.orderbook?.liquidityScore || '?';
388
- text += `${name.padEnd(36)} +${String(e.edge || 0).padStart(2)}¢ ${liq}\n`;
389
- }
390
- text += `</pre>`;
391
- return text;
392
- }
393
- async function handleBalance() {
394
- if (!(0, kalshi_js_1.isKalshiConfigured)())
395
- return '⚠️ Kalshi not configured.';
396
- const bal = await (0, kalshi_js_1.getBalance)();
397
- if (!bal)
398
- return '⚠️ Failed to fetch balance.';
399
- return `💰 Balance: <b>$${bal.balance.toFixed(2)}</b> | Portfolio: <b>$${bal.portfolioValue.toFixed(2)}</b>`;
400
- }
401
- async function handleOrders() {
402
- if (!(0, kalshi_js_1.isKalshiConfigured)())
403
- return '⚠️ Kalshi not configured.';
404
- const result = await (0, kalshi_js_1.getOrders)({ status: 'resting', limit: 20 });
405
- if (!result || !result.orders || result.orders.length === 0)
406
- return 'No resting orders.';
407
- let text = `📋 <b>Resting Orders</b>\n<pre>`;
408
- for (const o of result.orders) {
409
- const qty = Math.round(parseFloat(o.remaining_count_fp || '0'));
410
- const price = Math.round(parseFloat(o.yes_price_dollars || '0') * 100);
411
- const ticker = (o.ticker || '???').slice(0, 25);
412
- text += `${o.action} ${qty}x ${ticker} @ ${price}¢\n`;
413
- }
414
- text += `</pre>`;
415
- return text;
416
- }
417
- async function handleEval(client, thesisId) {
418
- await client.evaluate(thesisId);
419
- return '⚡ Evaluation triggered. Results in ~2 minutes.';
420
- }
421
- async function handleList(client) {
422
- const { theses } = await client.listTheses();
423
- if (!theses || theses.length === 0)
424
- return 'No theses.';
425
- let text = `📝 <b>Theses</b>\n<pre>`;
426
- for (const t of theses) {
427
- const conf = typeof t.confidence === 'number' ? Math.round(t.confidence * 100) : 0;
428
- const title = (t.rawThesis || t.thesis || '').slice(0, 50);
429
- text += `${t.id.slice(0, 8)} ${String(conf).padStart(3)}% ${t.status.padEnd(8)} ${title}\n`;
430
- }
431
- text += `</pre>`;
432
- return text;
433
- }
434
-
435
-
436
- /***/ }),
437
-
438
- /***/ 76342:
439
- /***/ ((__unused_webpack_module, exports) => {
440
-
441
-
442
- /**
443
- * Telegram message formatting utilities
444
- */
445
- Object.defineProperty(exports, "__esModule", ({ value: true }));
446
- exports.splitMessage = splitMessage;
447
- exports.fmtDollar = fmtDollar;
448
- exports.sparkline = sparkline;
449
- exports.escapeHtml = escapeHtml;
450
- /** Split long text into chunks under Telegram's 4096 char limit */
451
- function splitMessage(text, maxLen = 4000) {
452
- if (text.length <= maxLen)
453
- return [text];
454
- const chunks = [];
455
- let remaining = text;
456
- while (remaining.length > 0) {
457
- if (remaining.length <= maxLen) {
458
- chunks.push(remaining);
459
- break;
460
- }
461
- // Find a good split point (newline near maxLen)
462
- let splitAt = remaining.lastIndexOf('\n', maxLen);
463
- if (splitAt < maxLen * 0.5)
464
- splitAt = maxLen;
465
- chunks.push(remaining.slice(0, splitAt));
466
- remaining = remaining.slice(splitAt);
467
- }
468
- return chunks;
469
- }
470
- /** Format a number as dollars */
471
- function fmtDollar(cents) {
472
- const d = cents / 100;
473
- return d >= 0 ? `+$${d.toFixed(2)}` : `-$${Math.abs(d).toFixed(2)}`;
474
- }
475
- /** Simple sparkline */
476
- function sparkline(values) {
477
- if (values.length === 0)
478
- return '';
479
- const min = Math.min(...values);
480
- const max = Math.max(...values);
481
- const range = max - min || 1;
482
- const blocks = '▁▂▃▄▅▆▇█';
483
- return values.map(v => {
484
- const idx = Math.round(((v - min) / range) * 7);
485
- return blocks[idx];
486
- }).join('');
487
- }
488
- /** Escape HTML special characters for Telegram HTML parse mode */
489
- function escapeHtml(s) {
490
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
491
- }
492
-
493
-
494
- /***/ }),
495
-
496
- /***/ 55875:
497
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
498
-
499
-
500
- /**
501
- * Delta API poller — push confidence changes to Telegram
502
- */
503
- Object.defineProperty(exports, "__esModule", ({ value: true }));
504
- exports.startPoller = startPoller;
505
- const format_js_1 = __webpack_require__(76342);
506
- function startPoller(bot, chatId, client, thesisId) {
507
- let lastCheck = new Date().toISOString();
508
- return setInterval(async () => {
509
- try {
510
- const changes = await client.getChanges(thesisId, lastCheck);
511
- lastCheck = new Date().toISOString();
512
- if (!changes || !changes.changed)
513
- return;
514
- const delta = changes.confidenceDelta ?? changes.delta ?? 0;
515
- if (Math.abs(delta) < 0.02)
516
- return;
517
- const conf = typeof changes.confidence === 'number' ? Math.round(changes.confidence * 100) : '?';
518
- const arrow = delta > 0 ? '📈' : '📉';
519
- const sign = delta > 0 ? '+' : '';
520
- const summary = changes.summary || changes.latestSummary || '';
521
- let text = `${arrow} <b>Confidence ${sign}${Math.round(delta * 100)}% → ${conf}%</b>\n`;
522
- if (summary)
523
- text += `${(0, format_js_1.escapeHtml)(summary.slice(0, 200))}`;
524
- await bot.api.sendMessage(chatId, text, { parse_mode: 'HTML' });
525
- }
526
- catch {
527
- // Silent — polling errors should not crash the bot
528
- }
529
- }, 60_000); // every 60 seconds
530
- }
531
-
532
-
533
- /***/ })
534
-
535
- };
536
- ;
1
+ "use strict";exports.id=12,exports.ids=[12],exports.modules={6012:function(e,t,s){var a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.startBot=async function(e){const t=(0,l.loadConfig)(),a=e.token||t.telegramBotToken||process.env.TELEGRAM_BOT_TOKEN;a||(console.error("No Telegram bot token. Use --token, set TELEGRAM_BOT_TOKEN, or add to ~/.sf/config.json"),process.exit(1));const o=new c.SFClient(t.apiKey,t.apiUrl),f=new n.Bot(a),m=e.chatId||null,y=new Map;function w(e){return!m||e===m}f.command("start",async e=>{if(!w(e.chat.id))return;const t=g(e.chat.id);try{const{theses:s}=await o.listTheses(),a=(s||[]).filter(e=>"active"===e.status);a.length>0?(t.thesisId=a[0].id,await e.reply(`✅ Connected to SimpleFunctions\n\nActive thesis: <b>${(a[0].rawThesis||"").slice(0,60)}</b>\nID: <code>${a[0].id.slice(0,8)}</code>\n\nCommands:\n/context — thesis snapshot\n/positions — Kalshi positions\n/edges — top edges\n/balance — account balance\n/orders — resting orders\n/eval — trigger evaluation\n/list — all theses\n/switch — switch thesis\n\nOr just type naturally.`,{parse_mode:"HTML"}),y.has(e.chat.id)&&clearInterval(y.get(e.chat.id)),y.set(e.chat.id,(0,p.startPoller)(f,e.chat.id,o,t.thesisId))):await e.reply('No active theses found. Create one with `sf create "your thesis"`')}catch(t){await e.reply(`❌ Connection failed: ${t.message}\nCheck SF_API_KEY in ~/.sf/config.json`)}}),f.command("context",async e=>{if(!w(e.chat.id))return;const t=g(e.chat.id);if(t.thesisId)try{const s=await(0,h.handleContext)(o,t.thesisId);for(const t of(0,d.splitMessage)(s))await e.reply(t,{parse_mode:"HTML"})}catch(t){await e.reply(`❌ ${t.message}`)}else await e.reply("No thesis selected. Use /start or /switch <id>")}),f.command("positions",async e=>{if(w(e.chat.id))try{const t=await(0,h.handlePositions)();for(const s of(0,d.splitMessage)(t))await e.reply(s,{parse_mode:"HTML"})}catch(t){await e.reply(`❌ ${t.message}`)}}),f.command("edges",async e=>{if(w(e.chat.id))try{const t=await(0,h.handleEdges)(o);for(const s of(0,d.splitMessage)(t))await e.reply(s,{parse_mode:"HTML"})}catch(t){await e.reply(`❌ ${t.message}`)}}),f.command("balance",async e=>{if(w(e.chat.id))try{const t=await(0,h.handleBalance)();await e.reply(t,{parse_mode:"HTML"})}catch(t){await e.reply(`❌ ${t.message}`)}}),f.command("orders",async e=>{if(w(e.chat.id))try{const t=await(0,h.handleOrders)();for(const s of(0,d.splitMessage)(t))await e.reply(s,{parse_mode:"HTML"})}catch(t){await e.reply(`❌ ${t.message}`)}}),f.command("eval",async e=>{if(!w(e.chat.id))return;const t=g(e.chat.id);if(t.thesisId)try{const s=await(0,h.handleEval)(o,t.thesisId);await e.reply(s)}catch(t){await e.reply(`❌ ${t.message}`)}else await e.reply("No thesis selected.")}),f.command("list",async e=>{if(w(e.chat.id))try{const t=await(0,h.handleList)(o);for(const s of(0,d.splitMessage)(t))await e.reply(s,{parse_mode:"HTML"})}catch(t){await e.reply(`❌ ${t.message}`)}}),f.command("switch",async e=>{if(!w(e.chat.id))return;const t=g(e.chat.id),s=e.match?.trim();if(s)try{const{theses:a}=await o.listTheses(),n=(a||[]).find(e=>e.id.startsWith(s));n?(t.thesisId=n.id,t.agentMessages=[],y.has(e.chat.id)&&clearInterval(y.get(e.chat.id)),y.set(e.chat.id,(0,p.startPoller)(f,e.chat.id,o,t.thesisId)),await e.reply(`Switched to: <code>${n.id.slice(0,8)}</code> — ${(n.rawThesis||"").slice(0,60)}`,{parse_mode:"HTML"})):await e.reply(`No thesis found matching "${s}". Use /list to see all.`)}catch(t){await e.reply(`❌ ${t.message}`)}else await e.reply("Usage: /switch <thesis_id>")}),f.on("message:text",async e=>{if(!w(e.chat.id))return;const t=g(e.chat.id),a=e.message.text;if(!a.startsWith("/")){if(!t.thesisId)try{const{theses:s}=await o.listTheses(),a=(s||[]).filter(e=>"active"===e.status);if(!(a.length>0))return void await e.reply('No active theses. Create one with `sf create "your thesis"` then /start');t.thesisId=a[0].id}catch{return void await e.reply("Could not connect. Use /start first.")}try{await e.replyWithChatAction("typing");const{runAgentMessage:n}=await s.e(160).then(s.bind(s,28160)),i=new Promise((e,t)=>setTimeout(()=>t(new Error("Response timeout (30s)")),3e4)),r=await Promise.race([n(o,t,a),i]);if(r&&0!==r.trim().length)for(const t of(0,d.splitMessage)(r))try{await e.reply(t,{parse_mode:"HTML"})}catch{await e.reply(t)}else await e.reply("No response generated. Try rephrasing or use a slash command.")}catch(t){console.error("[Telegram] Agent error:",t.message),await e.reply(`❌ ${t.message}`)}}}),f.on("callback_query:data",async e=>{const t=e.callbackQuery.data;t.startsWith("order_confirm:")?await e.answerCallbackQuery({text:"Order execution coming soon"}):"order_cancel"===t&&(await e.answerCallbackQuery({text:"Order cancelled"}),await e.editMessageText("❌ Order cancelled."))}),console.log("🤖 SimpleFunctions Telegram bot starting..."),console.log(" SF API: "+(t.apiKey?"✓":"✗")),console.log(" Kalshi: "+(process.env.KALSHI_API_KEY_ID?"✓":"✗")),console.log(" OpenRouter: "+(t.openrouterKey?"✓":"✗")),m&&console.log(` Restricted to chat: ${m}`);function $(){for(const e of y.values())clearInterval(e);f.stop();try{i.default.unlinkSync(u)}catch{}}console.log(" Press Ctrl+C to stop.\n"),i.default.mkdirSync(r.default.dirname(u),{recursive:!0}),i.default.writeFileSync(u,String(process.pid)),process.on("uncaughtException",e=>{console.error("[Telegram] Uncaught exception:",e.message)}),process.on("unhandledRejection",e=>{console.error("[Telegram] Unhandled rejection:",e?.message||e)}),process.on("SIGINT",()=>{$(),process.exit(0)}),process.on("SIGTERM",()=>{$(),process.exit(0)}),process.on("exit",()=>{try{i.default.unlinkSync(u)}catch{}}),await f.start()};const n=s(53278),i=a(s(79896)),r=a(s(16928)),o=a(s(70857)),c=s(19218),l=s(11627),d=s(76342),h=s(77499),p=s(55875),u=r.default.join(o.default.homedir(),".sf","telegram.pid"),f=new Map;function g(e){return f.has(e)||f.set(e,{thesisId:null,agentMessages:[]}),f.get(e)}},77499:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.handleContext=async function(e,t){const s=await e.getContext(t),a="number"==typeof s.confidence?Math.round(100*s.confidence):"?",i=s.thesis||s.rawThesis||"N/A";let r=`📋 <b>${(0,n.escapeHtml)(i.slice(0,80))}</b>\n`;r+=`Confidence: <b>${a}%</b> | Status: ${s.status}\n\n`;const o=(s.edges||[]).slice(0,8);if(o.length>0){r+="<b>Top Edges:</b>\n<pre>";for(const e of o){r+=`${(e.market||e.marketTitle||"???").slice(0,35).padEnd(36)} ${String(e.edge||0).padStart(3)}¢ ${e.direction||"yes"}\n`}r+="</pre>"}return r},t.handlePositions=async function(){if(!(0,a.isKalshiConfigured)())return"⚠️ Kalshi not configured. Run <code>sf setup --kalshi</code>";const e=await(0,a.getPositions)();if(!e||0===e.length)return"No open positions.";let t=0;const s=[];for(const i of e){const e=await(0,a.getMarketPrice)(i.ticker)??i.average_price_paid,r=(e-i.average_price_paid)*i.quantity;t+=r;const o=(0,n.fmtDollar)(r);s.push(`${i.ticker.slice(0,28).padEnd(29)} ${String(i.quantity).padStart(5)} ${String(i.average_price_paid).padStart(3)}¢→${String(e).padStart(3)}¢ ${o}`)}let i="📊 <b>Positions</b>\n<pre>";return i+=s.join("\n"),i+=`\n${"─".repeat(50)}\nTotal P&L: ${(0,n.fmtDollar)(t)}`,i+="</pre>",i},t.handleEdges=async function(e){const{theses:t}=await e.listTheses(),s=(t||[]).filter(e=>"active"===e.status),a=await Promise.allSettled(s.map(async t=>((await e.getContext(t.id)).edges||[]).map(e=>({...e,thesisId:t.id})))),n=[];for(const e of a)"fulfilled"===e.status&&n.push(...e.value);n.sort((e,t)=>Math.abs(t.edge||0)-Math.abs(e.edge||0));const i=n.slice(0,10);if(0===i.length)return"No edges found.";let r="📈 <b>Top Edges</b>\n<pre>";for(const e of i){const t=(e.market||"???").slice(0,35),s=e.orderbook?.liquidityScore||"?";r+=`${t.padEnd(36)} +${String(e.edge||0).padStart(2)}¢ ${s}\n`}return r+="</pre>",r},t.handleBalance=async function(){if(!(0,a.isKalshiConfigured)())return"⚠️ Kalshi not configured.";const e=await(0,a.getBalance)();return e?`💰 Balance: <b>$${e.balance.toFixed(2)}</b> | Portfolio: <b>$${e.portfolioValue.toFixed(2)}</b>`:"⚠️ Failed to fetch balance."},t.handleOrders=async function(){if(!(0,a.isKalshiConfigured)())return"⚠️ Kalshi not configured.";const e=await(0,a.getOrders)({status:"resting",limit:20});if(!e||!e.orders||0===e.orders.length)return"No resting orders.";let t="📋 <b>Resting Orders</b>\n<pre>";for(const s of e.orders){const e=Math.round(parseFloat(s.remaining_count_fp||"0")),a=Math.round(100*parseFloat(s.yes_price_dollars||"0")),n=(s.ticker||"???").slice(0,25);t+=`${s.action} ${e}x ${n} @ ${a}¢\n`}return t+="</pre>",t},t.handleEval=async function(e,t){return await e.evaluate(t),"⚡ Evaluation triggered. Results in ~2 minutes."},t.handleList=async function(e){const{theses:t}=await e.listTheses();if(!t||0===t.length)return"No theses.";let s="📝 <b>Theses</b>\n<pre>";for(const e of t){const t="number"==typeof e.confidence?Math.round(100*e.confidence):0,a=(e.rawThesis||e.thesis||"").slice(0,50);s+=`${e.id.slice(0,8)} ${String(t).padStart(3)}% ${e.status.padEnd(8)} ${a}\n`}return s+="</pre>",s};const a=s(96139),n=s(76342)},76342:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.splitMessage=function(e,t=4e3){if(e.length<=t)return[e];const s=[];let a=e;for(;a.length>0;){if(a.length<=t){s.push(a);break}let e=a.lastIndexOf("\n",t);e<.5*t&&(e=t),s.push(a.slice(0,e)),a=a.slice(e)}return s},t.fmtDollar=function(e){const t=e/100;return t>=0?`+$${t.toFixed(2)}`:`-$${Math.abs(t).toFixed(2)}`},t.sparkline=function(e){if(0===e.length)return"";const t=Math.min(...e),s=Math.max(...e)-t||1;return e.map(e=>"▁▂▃▄▅▆▇█"[Math.round((e-t)/s*7)]).join("")},t.escapeHtml=function(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}},55875:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.startPoller=function(e,t,s,n){let i=(new Date).toISOString();return setInterval(async()=>{try{const r=await s.getChanges(n,i);if(i=(new Date).toISOString(),!r||!r.changed)return;const o=r.confidenceDelta??r.delta??0;if(Math.abs(o)<.02)return;const c="number"==typeof r.confidence?Math.round(100*r.confidence):"?",l=o>0?"📈":"📉",d=o>0?"+":"",h=r.summary||r.latestSummary||"";let p=`${l} <b>Confidence ${d}${Math.round(100*o)}% → ${c}%</b>\n`;h&&(p+=`${(0,a.escapeHtml)(h.slice(0,200))}`),await e.api.sendMessage(t,p,{parse_mode:"HTML"})}catch{}},6e4)};const a=s(76342)}};