@spfunctions/cli 1.7.19 → 1.7.22

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 -833
  15. package/package.json +5 -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 -246
  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 -116
  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 -10
  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 -52
  178. package/dist/utils.js +0 -146
  179. package/dist/utils.test.d.ts +0 -1
  180. package/dist/utils.test.js +0 -111
@@ -1,573 +0,0 @@
1
- "use strict";
2
- /**
3
- * Agent bridge — connects Telegram to pi-agent-core
4
- *
5
- * Uses the SAME tools as sf agent --plain. Multi-turn tool calling
6
- * is handled by pi-agent-core's Agent class (not manual OpenRouter calls).
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getOrCreateAgent = getOrCreateAgent;
10
- exports.runAgentMessage = runAgentMessage;
11
- const config_js_1 = require("../config.js");
12
- let piModules = null;
13
- async function loadPiModules() {
14
- if (piModules)
15
- return piModules;
16
- const [piAgent, piAi] = await Promise.all([
17
- import('@mariozechner/pi-agent-core'),
18
- import('@mariozechner/pi-ai'),
19
- ]);
20
- const typebox = await import('@sinclair/typebox');
21
- piModules = { Agent: piAgent.Agent, streamSimple: piAi.streamSimple, Type: typebox.Type };
22
- return piModules;
23
- }
24
- async function buildTools(sfClient, thesisId, latestContext) {
25
- const { Type } = await loadPiModules();
26
- const config = (0, config_js_1.loadConfig)();
27
- const emptyParams = Type.Object({});
28
- // Import Kalshi functions
29
- const kalshi = await import('../kalshi.js');
30
- const { kalshiFetchAllSeries, kalshiFetchMarketsBySeries, kalshiFetchMarket } = await import('../client.js');
31
- const tools = [
32
- {
33
- name: 'get_context', label: 'Context',
34
- description: 'Get thesis snapshot with causal tree, edges, evaluation',
35
- parameters: emptyParams,
36
- execute: async () => {
37
- const ctx = await sfClient.getContext(thesisId);
38
- return { content: [{ type: 'text', text: JSON.stringify(ctx, null, 2) }], details: {} };
39
- },
40
- },
41
- {
42
- name: 'global_context', label: 'Snapshot',
43
- description: 'Global market snapshot — movers, expiring, milestones, liquidity, signals. No thesis needed.',
44
- parameters: emptyParams,
45
- execute: async () => {
46
- const { fetchGlobalContext } = await import('../client.js');
47
- const data = await fetchGlobalContext();
48
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
49
- },
50
- },
51
- {
52
- name: 'query', label: 'Query',
53
- description: 'LLM-enhanced prediction market search. Returns answer, live prices, key factors.',
54
- parameters: Type.Object({ q: Type.String({ description: 'Question' }) }),
55
- execute: async (_id, p) => {
56
- const { fetchQuery } = await import('../client.js');
57
- const data = await fetchQuery(p.q);
58
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
59
- },
60
- },
61
- {
62
- name: 'get_markets', label: 'Markets',
63
- description: 'Traditional market prices: SPY, VIX, Treasury, Gold, Oil.',
64
- parameters: emptyParams,
65
- execute: async () => {
66
- const { fetchTraditionalMarkets } = await import('../client.js');
67
- const data = await fetchTraditionalMarkets();
68
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
69
- },
70
- },
71
- {
72
- name: 'inject_signal', label: 'Signal',
73
- description: 'Inject a signal (news, note, observation) into the thesis',
74
- parameters: Type.Object({
75
- content: Type.String({ description: 'Signal content' }),
76
- type: Type.Optional(Type.String({ description: 'news | user_note | external' })),
77
- }),
78
- execute: async (_id, p) => {
79
- await sfClient.injectSignal(thesisId, p.type || 'user_note', p.content, 'telegram');
80
- return { content: [{ type: 'text', text: 'Signal injected.' }], details: {} };
81
- },
82
- },
83
- {
84
- name: 'trigger_evaluation', label: 'Evaluate',
85
- description: 'Trigger a deep evaluation cycle',
86
- parameters: emptyParams,
87
- execute: async () => {
88
- await sfClient.evaluate(thesisId);
89
- return { content: [{ type: 'text', text: 'Evaluation triggered. Results in ~2 minutes.' }], details: {} };
90
- },
91
- },
92
- {
93
- name: 'scan_markets', label: 'Scan',
94
- description: 'Search Kalshi markets by keyword, series, or ticker',
95
- parameters: Type.Object({
96
- query: Type.Optional(Type.String({ description: 'Keyword search' })),
97
- series: Type.Optional(Type.String({ description: 'Series ticker' })),
98
- market: Type.Optional(Type.String({ description: 'Market ticker' })),
99
- }),
100
- execute: async (_id, p) => {
101
- let result;
102
- if (p.market) {
103
- result = await kalshiFetchMarket(p.market);
104
- }
105
- else if (p.series) {
106
- result = await kalshiFetchMarketsBySeries(p.series);
107
- }
108
- else if (p.query) {
109
- const series = await kalshiFetchAllSeries();
110
- const kws = p.query.toLowerCase().split(/\s+/);
111
- result = series.filter((s) => kws.every((k) => ((s.title || '') + (s.ticker || '')).toLowerCase().includes(k)))
112
- .filter((s) => parseFloat(s.volume_fp || '0') > 1000)
113
- .sort((a, b) => parseFloat(b.volume_fp || '0') - parseFloat(a.volume_fp || '0'))
114
- .slice(0, 10);
115
- }
116
- else {
117
- result = { error: 'Provide query, series, or market' };
118
- }
119
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], details: {} };
120
- },
121
- },
122
- {
123
- name: 'list_theses', label: 'List',
124
- description: 'List all theses',
125
- parameters: emptyParams,
126
- execute: async () => {
127
- const theses = await sfClient.listTheses();
128
- return { content: [{ type: 'text', text: JSON.stringify(theses, null, 2) }], details: {} };
129
- },
130
- },
131
- {
132
- name: 'get_positions', label: 'Positions',
133
- description: 'Get Kalshi positions with live prices and P&L',
134
- parameters: emptyParams,
135
- execute: async () => {
136
- const positions = await kalshi.getPositions();
137
- if (!positions)
138
- return { content: [{ type: 'text', text: 'Kalshi not configured.' }], details: {} };
139
- for (const pos of positions) {
140
- const price = await kalshi.getMarketPrice(pos.ticker);
141
- if (price != null) {
142
- pos.current_value = price;
143
- pos.unrealized_pnl = Math.round((price - pos.average_price_paid) * pos.quantity);
144
- }
145
- }
146
- const formatted = positions.map((p) => ({
147
- ticker: p.ticker, qty: p.quantity,
148
- avg_price: `${p.average_price_paid}¢`, current: `${p.current_value}¢`,
149
- pnl: `$${((p.unrealized_pnl || 0) / 100).toFixed(2)}`,
150
- }));
151
- return { content: [{ type: 'text', text: JSON.stringify(formatted, null, 2) }], details: {} };
152
- },
153
- },
154
- {
155
- name: 'get_balance', label: 'Balance',
156
- description: 'Get Kalshi account balance',
157
- parameters: emptyParams,
158
- execute: async () => {
159
- const bal = await kalshi.getBalance();
160
- if (!bal)
161
- return { content: [{ type: 'text', text: 'Kalshi not configured.' }], details: {} };
162
- return { content: [{ type: 'text', text: JSON.stringify(bal, null, 2) }], details: {} };
163
- },
164
- },
165
- {
166
- name: 'get_orders', label: 'Orders',
167
- description: 'Get resting orders on Kalshi',
168
- parameters: emptyParams,
169
- execute: async () => {
170
- const result = await kalshi.getOrders({ status: 'resting', limit: 50 });
171
- if (!result)
172
- return { content: [{ type: 'text', text: 'Kalshi not configured.' }], details: {} };
173
- return { content: [{ type: 'text', text: JSON.stringify(result.orders, null, 2) }], details: {} };
174
- },
175
- },
176
- {
177
- name: 'get_fills', label: 'Fills',
178
- description: 'Get recent trade fills',
179
- parameters: Type.Object({ ticker: Type.Optional(Type.String()) }),
180
- execute: async (_id, p) => {
181
- const result = await kalshi.getFills({ ticker: p.ticker, limit: 20 });
182
- if (!result)
183
- return { content: [{ type: 'text', text: 'Kalshi not configured.' }], details: {} };
184
- return { content: [{ type: 'text', text: JSON.stringify(result.fills, null, 2) }], details: {} };
185
- },
186
- },
187
- {
188
- name: 'get_settlements', label: 'Settlements',
189
- description: 'Get settled contracts with P&L',
190
- parameters: emptyParams,
191
- execute: async () => {
192
- const result = await kalshi.getSettlements({ limit: 50 });
193
- if (!result)
194
- return { content: [{ type: 'text', text: 'Kalshi not configured.' }], details: {} };
195
- return { content: [{ type: 'text', text: JSON.stringify(result.settlements, null, 2) }], details: {} };
196
- },
197
- },
198
- {
199
- name: 'get_edges', label: 'Edges',
200
- description: 'Top 10 edges across all active theses',
201
- parameters: emptyParams,
202
- execute: async () => {
203
- const { theses } = await sfClient.listTheses();
204
- const active = (theses || []).filter((t) => t.status === 'active');
205
- const results = await Promise.allSettled(active.map(async (t) => {
206
- const ctx = await sfClient.getContext(t.id);
207
- return (ctx.edges || []).map((e) => ({ ...e, thesisId: t.id }));
208
- }));
209
- const allEdges = [];
210
- for (const r of results) {
211
- if (r.status === 'fulfilled')
212
- allEdges.push(...r.value);
213
- }
214
- allEdges.sort((a, b) => Math.abs(b.edge || 0) - Math.abs(a.edge || 0));
215
- return { content: [{ type: 'text', text: JSON.stringify(allEdges.slice(0, 10), null, 2) }], details: {} };
216
- },
217
- },
218
- {
219
- name: 'get_schedule', label: 'Schedule',
220
- description: 'Exchange status (open/closed) and trading hours',
221
- parameters: emptyParams,
222
- execute: async () => {
223
- const res = await fetch('https://api.elections.kalshi.com/trade-api/v2/exchange/status', { headers: { 'Accept': 'application/json' } });
224
- if (!res.ok)
225
- return { content: [{ type: 'text', text: `API error: ${res.status}` }], details: {} };
226
- const data = await res.json();
227
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
228
- },
229
- },
230
- {
231
- name: 'get_feed', label: 'Feed',
232
- description: 'Recent evaluation history',
233
- parameters: Type.Object({ hours: Type.Optional(Type.Number({ description: 'Hours of history (default 24)' })) }),
234
- execute: async (_id, p) => {
235
- const data = await sfClient.getFeed(p.hours || 24);
236
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
237
- },
238
- },
239
- {
240
- name: 'web_search', label: 'Search',
241
- description: 'Search the web for latest news',
242
- parameters: Type.Object({ query: Type.String({ description: 'Search query' }) }),
243
- execute: async (_id, p) => {
244
- const tavilyKey = process.env.TAVILY_API_KEY || config.tavilyKey;
245
- const tgSfKey = config.apiKey || process.env.SF_API_KEY;
246
- const tgSfUrl = config.apiUrl || process.env.SF_API_URL || 'https://simplefunctions.dev';
247
- const canProxySearch = !tavilyKey && tgSfKey;
248
- if (!tavilyKey && !canProxySearch)
249
- return { content: [{ type: 'text', text: 'Web search not available. Run sf login or set TAVILY_API_KEY.' }], details: {} };
250
- let res;
251
- if (tavilyKey) {
252
- res = await fetch('https://api.tavily.com/search', {
253
- method: 'POST', headers: { 'Content-Type': 'application/json' },
254
- body: JSON.stringify({ api_key: tavilyKey, query: p.query, max_results: 3, search_depth: 'basic', include_answer: true }),
255
- });
256
- }
257
- else {
258
- res = await fetch(`${tgSfUrl}/api/proxy/search`, {
259
- method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tgSfKey}` },
260
- body: JSON.stringify({ query: p.query, max_results: 3, search_depth: 'basic', include_answer: true }),
261
- });
262
- }
263
- if (!res.ok)
264
- return { content: [{ type: 'text', text: `Search failed: ${res.status}` }], details: {} };
265
- const data = await res.json();
266
- const answer = data.answer ? `Summary: ${data.answer}\n\n` : '';
267
- const results = (data.results || []).map((r) => `${r.title}: ${(r.content || '').slice(0, 150)}`).join('\n\n');
268
- return { content: [{ type: 'text', text: `${answer}${results}` }], details: {} };
269
- },
270
- },
271
- {
272
- name: 'create_thesis', label: 'Create',
273
- description: 'Create a new thesis',
274
- parameters: Type.Object({ rawThesis: Type.String({ description: 'Thesis statement' }) }),
275
- execute: async (_id, p) => {
276
- const result = await sfClient.createThesis(p.rawThesis, true);
277
- return { content: [{ type: 'text', text: `Created: ${result.id}\nConfidence: ${Math.round((result.confidence || 0.5) * 100)}%` }], details: {} };
278
- },
279
- },
280
- {
281
- name: 'what_if', label: 'What-If',
282
- description: 'Override causal tree node probabilities and see how edges change. Zero LLM cost.',
283
- parameters: Type.Object({
284
- overrides: Type.Array(Type.Object({
285
- nodeId: Type.String({ description: 'Node ID (e.g. n1, n3.1)' }),
286
- newProbability: Type.Number({ description: 'New probability 0-1' }),
287
- })),
288
- }),
289
- execute: async (_id, p) => {
290
- const ctx = latestContext;
291
- const allNodes = [];
292
- function flatten(nodes) { for (const n of nodes) {
293
- allNodes.push(n);
294
- if (n.children?.length)
295
- flatten(n.children);
296
- } }
297
- flatten(ctx?.causalTree?.nodes || []);
298
- const overrideMap = new Map(p.overrides.map((o) => [o.nodeId, o.newProbability]));
299
- const topNodes = (ctx?.causalTree?.nodes || []).filter((n) => !n.parentId || n.depth === 0);
300
- const oldConf = topNodes.reduce((s, n) => s + (n.probability || 0.5), 0) / Math.max(topNodes.length, 1);
301
- for (const n of allNodes) {
302
- if (overrideMap.has(n.id))
303
- n.probability = overrideMap.get(n.id);
304
- }
305
- const newConf = topNodes.reduce((s, n) => s + (n.probability || 0.5), 0) / Math.max(topNodes.length, 1);
306
- const edges = (ctx?.edges || []).slice(0, 10).map((e) => {
307
- const node = allNodes.find((n) => n.id === e.relatedNodeId);
308
- if (!node || !overrideMap.has(node.id))
309
- return { market: e.market, oldEdge: e.edge, newEdge: e.edge, signal: 'unchanged' };
310
- const scale = node.probability / (e.confidence || 0.5);
311
- const newThesis = Math.round(e.thesisPrice * scale);
312
- const newEdge = newThesis - e.marketPrice;
313
- return { market: e.market, oldEdge: e.edge, newEdge, signal: Math.abs(newEdge - e.edge) < 1 ? 'unchanged' : 'changed' };
314
- }).filter((e) => e.signal !== 'unchanged');
315
- return { content: [{ type: 'text', text: JSON.stringify({ confidence: { old: Math.round(oldConf * 100), new: Math.round(newConf * 100) }, edges }, null, 2) }], details: {} };
316
- },
317
- },
318
- ];
319
- // ── X (Twitter) tools ──
320
- tools.push({
321
- name: 'search_x', label: 'X Search',
322
- description: 'Search X (Twitter) for recent discussions. Returns posts, sentiment, themes.',
323
- parameters: Type.Object({
324
- query: Type.String({ description: 'Search query' }),
325
- mode: Type.Optional(Type.String({ description: '"summary" or "raw"' })),
326
- hours: Type.Optional(Type.Number({ description: 'Hours (default 24)' })),
327
- }),
328
- execute: async (_id, p) => {
329
- const data = await sfClient.searchX(p.query, { mode: p.mode, hours: p.hours });
330
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
331
- },
332
- }, {
333
- name: 'x_volume', label: 'X Volume',
334
- description: 'X discussion volume trend — total posts, velocity, peak time.',
335
- parameters: Type.Object({
336
- query: Type.String({ description: 'Search query' }),
337
- hours: Type.Optional(Type.Number({ description: 'Hours (default 72)' })),
338
- }),
339
- execute: async (_id, p) => {
340
- const data = await sfClient.getXVolume(p.query, { hours: p.hours });
341
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
342
- },
343
- }, {
344
- name: 'x_news', label: 'X News',
345
- description: 'News stories trending on X — titles, summaries, tickers.',
346
- parameters: Type.Object({
347
- query: Type.String({ description: 'Search query' }),
348
- limit: Type.Optional(Type.Number({ description: 'Max stories (default 10)' })),
349
- }),
350
- execute: async (_id, p) => {
351
- const data = await sfClient.searchXNews(p.query, { limit: p.limit });
352
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
353
- },
354
- }, {
355
- name: 'x_account', label: 'X Account',
356
- description: 'Recent posts from a specific X account.',
357
- parameters: Type.Object({
358
- username: Type.String({ description: 'X username (with or without @)' }),
359
- hours: Type.Optional(Type.Number({ description: 'Hours (default 24)' })),
360
- }),
361
- execute: async (_id, p) => {
362
- const data = await sfClient.getXAccount(p.username, { hours: p.hours });
363
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
364
- },
365
- }, {
366
- name: 'heartbeat_config', label: 'Heartbeat Config',
367
- description: 'View or update heartbeat settings: scan intervals, model tier, budget cap, pause/resume. Shows cost breakdown.',
368
- parameters: Type.Object({
369
- thesisId: Type.String({ description: 'Thesis ID' }),
370
- newsIntervalMin: Type.Optional(Type.Number({ description: 'News scan interval (15-1440 min)' })),
371
- xIntervalMin: Type.Optional(Type.Number({ description: 'X scan interval (60-1440 min)' })),
372
- evalModelTier: Type.Optional(Type.String({ description: 'cheap, medium, or heavy' })),
373
- monthlyBudgetUsd: Type.Optional(Type.Number({ description: 'Monthly budget (0 = unlimited)' })),
374
- paused: Type.Optional(Type.Boolean({ description: 'Pause or resume heartbeat' })),
375
- }),
376
- execute: async (_id, p) => {
377
- const hasUp = p.newsIntervalMin || p.xIntervalMin || p.evalModelTier || p.monthlyBudgetUsd !== undefined || p.paused !== undefined;
378
- if (hasUp) {
379
- const u = {};
380
- if (p.newsIntervalMin)
381
- u.newsIntervalMin = p.newsIntervalMin;
382
- if (p.xIntervalMin)
383
- u.xIntervalMin = p.xIntervalMin;
384
- if (p.evalModelTier)
385
- u.evalModelTier = p.evalModelTier;
386
- if (p.monthlyBudgetUsd !== undefined)
387
- u.monthlyBudgetUsd = p.monthlyBudgetUsd;
388
- if (p.paused !== undefined)
389
- u.paused = p.paused;
390
- await sfClient.updateHeartbeatConfig(p.thesisId, u);
391
- }
392
- const data = await sfClient.getHeartbeatConfig(p.thesisId);
393
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
394
- },
395
- });
396
- // Trading tools (only if enabled)
397
- if (config.tradingEnabled) {
398
- tools.push({
399
- name: 'place_order',
400
- label: 'Place Order',
401
- description: 'Place a buy or sell order on Kalshi. Requires trading to be enabled. Always confirm with user before placing.',
402
- parameters: Type.Object({
403
- ticker: Type.String({ description: 'Market ticker (e.g. KXWTIMAX-26DEC31-T135)' }),
404
- side: Type.String({ description: 'yes or no' }),
405
- action: Type.String({ description: 'buy or sell' }),
406
- count: Type.Number({ description: 'Number of contracts' }),
407
- yes_price: Type.Number({ description: 'Price in cents (1-99)' }),
408
- }),
409
- execute: async (_id, p) => {
410
- try {
411
- const result = await kalshi.createOrder({
412
- ticker: p.ticker,
413
- side: p.side,
414
- action: p.action,
415
- type: 'limit',
416
- count: p.count,
417
- yes_price: p.yes_price,
418
- });
419
- return { content: [{ type: 'text', text: `✓ Order placed: ${(result.order || result).order_id || 'OK'}\n${p.action.toUpperCase()} ${p.count}x ${p.ticker} ${p.side.toUpperCase()} @ ${p.yes_price}¢` }], details: {} };
420
- }
421
- catch (err) {
422
- return { content: [{ type: 'text', text: `✗ Order failed: ${err.message}` }], details: {} };
423
- }
424
- },
425
- }, {
426
- name: 'cancel_order',
427
- label: 'Cancel Order',
428
- description: 'Cancel a resting order on Kalshi.',
429
- parameters: Type.Object({
430
- orderId: Type.String({ description: 'Order ID to cancel' }),
431
- }),
432
- execute: async (_id, p) => {
433
- try {
434
- await kalshi.cancelOrder(p.orderId);
435
- return { content: [{ type: 'text', text: `✓ Order ${p.orderId} cancelled.` }], details: {} };
436
- }
437
- catch (err) {
438
- return { content: [{ type: 'text', text: `✗ Cancel failed: ${err.message}` }], details: {} };
439
- }
440
- },
441
- });
442
- }
443
- return tools;
444
- }
445
- async function getOrCreateAgent(sfClient, session) {
446
- if (session.agent)
447
- return session.agent;
448
- const { Agent, streamSimple } = await loadPiModules();
449
- const piAi = await import('@mariozechner/pi-ai');
450
- const { getModel } = piAi;
451
- const config = (0, config_js_1.loadConfig)();
452
- const directOrKey = config.openrouterKey || process.env.OPENROUTER_API_KEY;
453
- const sfApiKey = config.apiKey || process.env.SF_API_KEY;
454
- const sfApiUrl = config.apiUrl || process.env.SF_API_URL || 'https://simplefunctions.dev';
455
- const tgUseProxy = !directOrKey && !!sfApiKey;
456
- const openrouterKey = directOrKey || sfApiKey;
457
- if (!openrouterKey)
458
- throw new Error('Need API key. Run sf login or sf setup.');
459
- const ctx = await sfClient.getContext(session.thesisId);
460
- const conf = typeof ctx.confidence === 'number' ? Math.round(ctx.confidence * 100) : 50;
461
- const tools = await buildTools(sfClient, session.thesisId, ctx);
462
- // Resolve model object (not just a string — pi-ai needs api/provider/baseUrl)
463
- const modelName = config.model || 'anthropic/claude-sonnet-4.6';
464
- let model;
465
- try {
466
- model = getModel('openrouter', modelName);
467
- }
468
- catch {
469
- model = {
470
- modelId: modelName, provider: 'openrouter', api: 'openai-completions',
471
- baseUrl: 'https://openrouter.ai/api/v1', id: modelName, name: modelName,
472
- inputPrice: 0, outputPrice: 0, contextWindow: 200000,
473
- supportsImages: true, supportsTools: true,
474
- };
475
- }
476
- if (tgUseProxy)
477
- model.baseUrl = `${sfApiUrl}/api/proxy`;
478
- const edgesSummary = (ctx.edges || [])
479
- .sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
480
- .slice(0, 5)
481
- .map((e) => ` ${(e.market || '').slice(0, 35)} | ${e.venue || 'kalshi'} | mkt ${e.marketPrice}¢ → thesis ${e.thesisPrice}¢ | edge ${e.edge > 0 ? '+' : ''}${e.edge}`)
482
- .join('\n') || ' (no edges)';
483
- const nodesSummary = (ctx.causalTree?.nodes || [])
484
- .filter((n) => n.depth === 0 || !n.depth)
485
- .slice(0, 5)
486
- .map((n) => ` ${n.id} ${(n.label || '').slice(0, 35)} — ${Math.round((n.probability || 0.5) * 100)}%`)
487
- .join('\n') || ' (no causal tree)';
488
- const systemPrompt = `You are a prediction market trading assistant on Telegram. Your job is to help the user see reality clearly and make correct trading decisions.
489
-
490
- ## Framework
491
- Edge = thesis price - market price. Positive = market underprices.
492
- Edge types: [consensus] depth>=500 real opponents, [attention] depth<100 illusory, [timing] market lags, [risk_premium] platform risk. Tag when reporting.
493
- Price: depth >= 500 = consensus, < 100 = unreliable, spread > 5 = noisy.
494
-
495
- ## Rules
496
- - When reporting an edge, tag it: [consensus] [attention] [timing] [risk_premium].
497
- - If any kill condition is triggered or approaching, lead with that.
498
- - If nothing material to do, say "quiet — no action needed." Don't pad.
499
- - Keep Telegram messages SHORT — bullet points, no walls of text.
500
- - Prices in cents (¢). P&L in dollars ($). Don't re-convert tool output units.
501
- - Call tools for fresh data. Never guess prices or P&L from this prompt.
502
- - Use search_x for X/Twitter sentiment. Use x_volume for discussion spikes. Use x_account to track key people.
503
- - Use heartbeat_config to view/adjust scan intervals, model tier, budget cap, or check cost breakdown.
504
- - You don't know user's positions. Call get_positions before discussing trades.
505
- - If user mentions news, inject_signal immediately. Don't ask "should I?"
506
- - If user says "evaluate" or "run it", trigger immediately.
507
- - Don't end with "anything else?" — user will ask.
508
- - Use Chinese if user writes Chinese, English if English.
509
- ${config.tradingEnabled ? '- Trading ENABLED. You have place_order and cancel_order. ALWAYS confirm before placing.' : '- Trading DISABLED. Tell user: sf setup --enable-trading'}
510
-
511
- ## Current State
512
- Thesis: ${(ctx.thesis || ctx.rawThesis || 'N/A').slice(0, 200)}
513
- ID: ${session.thesisId.slice(0, 8)} | Confidence: ${conf}%
514
-
515
- Causal nodes:
516
- ${nodesSummary}
517
-
518
- Top edges:
519
- ${edgesSummary}
520
-
521
- ${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice(0, 200)}` : ''}`;
522
- const agent = new Agent({
523
- initialState: {
524
- systemPrompt,
525
- model,
526
- tools,
527
- thinkingLevel: 'off',
528
- },
529
- streamFn: streamSimple,
530
- getApiKey: (provider) => provider === 'openrouter' ? openrouterKey : undefined,
531
- });
532
- // Restore session messages if available
533
- if (session.agentMessages.length > 0) {
534
- try {
535
- agent.replaceMessages(session.agentMessages);
536
- }
537
- catch { /* start fresh */ }
538
- }
539
- session.agent = agent;
540
- return agent;
541
- }
542
- async function runAgentMessage(client, session, userMessage) {
543
- const agent = await getOrCreateAgent(client, session);
544
- return new Promise((resolve, reject) => {
545
- let response = '';
546
- let toolLog = '';
547
- const timeout = setTimeout(() => {
548
- resolve(response || toolLog || 'Response timeout (45s). Try a simpler question.');
549
- }, 45_000);
550
- const unsub = agent.subscribe((event) => {
551
- if (event.type === 'message_update') {
552
- const e = event.assistantMessageEvent;
553
- if (e.type === 'text_delta')
554
- response += e.delta;
555
- }
556
- if (event.type === 'tool_execution_start') {
557
- toolLog += `⚡ ${event.toolName}\n`;
558
- }
559
- if (event.type === 'agent_end') {
560
- clearTimeout(timeout);
561
- unsub?.();
562
- // Save messages for session continuity
563
- session.agentMessages = agent.state.messages || [];
564
- resolve(response || toolLog || '(no response)');
565
- }
566
- });
567
- agent.prompt(userMessage).catch((err) => {
568
- clearTimeout(timeout);
569
- unsub?.();
570
- reject(err);
571
- });
572
- });
573
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Telegram Bot — main entry point
3
- *
4
- * Handles slash commands (zero LLM) and free text (via pi-agent-core).
5
- * Polls delta API for push notifications.
6
- */
7
- export declare function startBot(opts: {
8
- token?: string;
9
- chatId?: number;
10
- }): Promise<void>;