@spfunctions/cli 2.0.5 → 2.0.7

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/README.md CHANGED
@@ -1,109 +1,245 @@
1
1
  # SimpleFunctions CLI (`sf`)
2
2
 
3
- Prediction market intelligence CLI. Build causal thesis models, scan Kalshi/Polymarket for mispricings, detect edges, and tradeall from the terminal.
3
+ A terminal-native prediction market agent. Builds causal models of the world, scans 4,000+ Kalshi and Polymarket contracts for mispricings, and trades on your thesis autonomously or with you in the loop.
4
4
 
5
- ![demo](https://raw.githubusercontent.com/spfunctions/simplefunctions-cli/main/demo.gif)
5
+ ```
6
+ $ sf world
7
+ # World State — 2026-04-05T05:38 UTC
8
+ SF Index: Uncertainty 22/100 | Geopolitical Risk 53/100
9
+
10
+ Markets: SPY $655.94 (+0.12%) | GLD $429.08 (-1.79%) | USO $138.18 (+11.99%)
11
+
12
+ ## Geopolitical
13
+ - Will the U.S. invade Iran before 2027?: 54c (-7c)
14
+ - Russia x Ukraine ceasefire by end of 2026?: 30c
15
+
16
+ ## Economy
17
+ - US recession by end of 2026?: 28c
18
+ - How many Fed rate cuts in 2026?: 0 (0 bps): 36c
19
+ ```
6
20
 
7
- ## Quick Start
21
+ ## Install
8
22
 
9
23
  ```bash
10
24
  npm install -g @spfunctions/cli
11
- sf setup # interactive config wizard
12
- sf list # see your theses
13
- sf context <id> --json # get thesis state as JSON
25
+ sf login # browser auth (recommended)
26
+ sf status # verify everything works
27
+ ```
28
+
29
+ ## What it does
30
+
31
+ **World model** — Real-time prediction index distilled from thousands of markets. One command gives any LLM situational awareness.
32
+
33
+ ```bash
34
+ sf world # ~800 token world state snapshot
35
+ sf world --delta # what changed since last check (~30 tokens)
36
+ sf world --focus energy,geo # deep coverage on specific topics
37
+ sf ideas # S&T-style trade ideas with catalyst + risk
38
+ ```
39
+
40
+ **Thesis engine** — Structured causal models with Bayesian node trees, continuous evaluation against live market data, and automatic edge detection.
41
+
42
+ ```bash
43
+ sf create "Oil will exceed $120 by Q3 due to Hormuz disruption"
44
+ sf list # all theses with confidence + status
45
+ sf context <id> # causal tree + top edges + positions
46
+ sf evaluate <id> # trigger deep re-evaluation
47
+ sf signal <id> "OPEC cuts" # inject a signal for next eval cycle
48
+ ```
49
+
50
+ **Edge detection** — Cross-venue scanner compares your model's probabilities against Kalshi and Polymarket prices.
51
+
52
+ ```bash
53
+ sf edges # top mispricings across all theses
54
+ sf scan "recession" # search 4,000+ markets by keyword
55
+ sf book KXRECSSNBER-26 # orderbook depth
56
+ sf watch oil orderbook # live terminal orderbook stream
57
+ ```
58
+
59
+ **Intent system** — Declarative execution: describe what you want, the runtime handles when and how.
60
+
61
+ ```bash
62
+ sf intent buy KXRECSSNBER-26 10 --price 30 --trigger below:28 --auto
63
+ sf intent list # active intents + fill status
64
+ sf runtime start # daemon evaluates triggers, executes orders
65
+ ```
66
+
67
+ **Interactive agent** — Natural language interface with 30+ tools. Maintains a persistent workspace, shares context across sessions, and can schedule its own future actions.
68
+
69
+ ```bash
70
+ sf agent <id> # TUI agent with tool calling
71
+ sf agent <id> --once "summarize my portfolio risk" # single-shot
72
+ sf agent <id> --plain # readline mode, no TUI
73
+ sf telegram --daemon # Telegram bot (text + voice)
74
+ ```
75
+
76
+ **X / Twitter intelligence** — Social signal layer.
77
+
78
+ ```bash
79
+ sf x "tariff impact" # discussion summary + top posts
80
+ sf x-volume "iran oil" # volume and velocity trend
81
+ sf x-news "fed rate" # Grok-aggregated news stories
82
+ sf x-account @elikinosian # recent posts from an account
14
83
  ```
15
84
 
16
85
  ## Setup
17
86
 
18
- ### Interactive (recommended)
87
+ ### Quick (browser login)
19
88
 
20
89
  ```bash
21
- sf setup
90
+ sf login
22
91
  ```
23
92
 
24
- This walks you through:
25
- 1. **SF API key** (required) — get one at [simplefunctions.dev](https://simplefunctions.dev)
26
- 2. **Kalshi credentials** (optional) — for positions, trading, and orderbook data
27
- 3. **Trading mode** (optional) — enable `sf buy`/`sf sell` commands
93
+ ### Power user
28
94
 
29
- Config is saved to `~/.sf/config.json`. Environment variables override config values.
95
+ ```bash
96
+ sf setup # interactive wizard: API key, Kalshi, Polymarket, trading
97
+ sf setup --check # show config status
98
+ sf setup --polymarket # configure Polymarket wallet
99
+ sf setup --enable-trading # unlock buy/sell/intent commands
100
+ ```
30
101
 
31
- ### Manual
102
+ Config is saved to `~/.sf/config.json`. Environment variables override config values:
32
103
 
33
104
  ```bash
34
- export SF_API_KEY=sf_live_xxx # required
35
- export KALSHI_API_KEY_ID=xxx # optional, for positions/trading
36
- export KALSHI_PRIVATE_KEY_PATH=~/.ssh/kalshi.pem # optional, for positions/trading
105
+ export SF_API_KEY=sf_live_xxx
106
+ export KALSHI_API_KEY_ID=xxx
107
+ export KALSHI_PRIVATE_KEY_PATH=~/.kalshi/private.pem
37
108
  ```
38
109
 
39
110
  ### Verify
40
111
 
41
112
  ```bash
42
- sf setup --check # show current config status
43
- sf list # should show your theses
113
+ $ sf status
114
+ API ok simplefunctions.dev 252ms
115
+ Auth ok sf_live_8838...
116
+ Kalshi ok $156.42 balance
117
+ Polymarket ok 0xd53b9354...
118
+ Theses ok 4 active, 4 total
119
+ Trading ok enabled
120
+ Runtime -- not running (sf runtime start)
121
+ Telegram ok PID 84372
44
122
  ```
45
123
 
46
- ## Commands
124
+ ## Command Reference
47
125
 
48
- ### Thesis Management
126
+ ### World Model (no auth required)
49
127
 
50
128
  | Command | Description |
51
129
  |---------|-------------|
52
- | `sf list` | List all theses with status, confidence, and update time |
53
- | `sf get <id>` | Full thesis details: causal tree, edges, positions, last evaluation |
54
- | `sf context <id>` | Compact context snapshot (primary command for agents) |
55
- | `sf create "thesis"` | Create a new thesis (waits for formation by default) |
56
- | `sf signal <id> "text"` | Inject a signal (news, observation) for next evaluation |
57
- | `sf evaluate <id>` | Trigger deep evaluation with heavy model |
58
- | `sf publish <id>` | Make thesis publicly viewable |
59
- | `sf unpublish <id>` | Remove from public view |
130
+ | `sf world` | Prediction index: markets + geopolitics + economy + crypto + elections |
131
+ | `sf world --delta` | Changes since last check (~30 tokens, ideal for agent polling) |
132
+ | `sf world --focus <topics>` | Deep coverage on specific topics |
133
+ | `sf ideas` | Trade ideas with conviction, catalyst, direction, risk |
134
+ | `sf query "question"` | LLM-enhanced market knowledge search |
135
+ | `sf markets` | Traditional markets: SPY, VIX, bonds, gold, oil |
60
136
 
61
- ### Market Exploration (no auth required)
137
+ ### Thesis
62
138
 
63
139
  | Command | Description |
64
140
  |---------|-------------|
65
- | `sf scan "keywords"` | Search Kalshi markets by keyword |
66
- | `sf scan --series KXWTIMAX` | List all markets in a series |
67
- | `sf scan --market TICKER` | Get single market detail |
68
- | `sf explore` | Browse public theses |
141
+ | `sf list` | All theses with status, confidence, last update |
142
+ | `sf get <id>` | Full details: causal tree, edges, evaluation history |
143
+ | `sf context [id]` | No id: global market snapshot. With id: thesis context |
144
+ | `sf create "thesis"` | Create a new thesis |
145
+ | `sf signal <id> "text"` | Inject signal for next evaluation |
146
+ | `sf evaluate <id>` | Trigger deep re-evaluation |
147
+ | `sf augment <id>` | Evolve causal tree with new nodes |
148
+ | `sf heartbeat <id>` | View/configure autonomous evaluation schedule + costs |
149
+ | `sf publish` / `sf unpublish <id>` | Manage public visibility |
150
+
151
+ ### Markets
69
152
 
70
- ### Portfolio & Trading (requires Kalshi credentials)
153
+ | Command | Description |
154
+ |---------|-------------|
155
+ | `sf scan "keywords"` | Search Kalshi + Polymarket by keyword |
156
+ | `sf scan --series TICKER` | Browse all markets in a Kalshi series |
157
+ | `sf edges [--json]` | Top mispricings across all theses |
158
+ | `sf watch [query] [mode]` | Live market watch (modes: orderbook, flow, cross-venue, all) |
159
+ | `sf book <ticker> [...]` | Orderbook depth for specific markets |
160
+ | `sf whatif <id>` | What-if scenario analysis on causal tree |
161
+ | `sf liquidity [topic]` | Orderbook liquidity scanner |
162
+ | `sf forecast <event>` | Market distribution (P50/P75/P90) |
163
+ | `sf explore [slug]` | Browse public theses |
164
+
165
+ ### Portfolio
71
166
 
72
167
  | Command | Description |
73
168
  |---------|-------------|
74
- | `sf edges` | Top edges across all theses what to trade now |
75
- | `sf positions` | Current positions with P&L and edge overlay |
169
+ | `sf positions` | Kalshi + Polymarket positions with P&L |
76
170
  | `sf balance` | Account balance |
77
171
  | `sf orders` | Resting (open) orders |
78
172
  | `sf fills` | Recent trade fills |
79
- | `sf performance` | P&L over time with thesis event annotations |
80
173
  | `sf settlements` | Settled contracts with final P&L |
81
- | `sf liquidity` | Market liquidity scanner by topic |
174
+ | `sf performance` | P&L over time with event annotations |
175
+ | `sf dashboard` | Interactive TUI portfolio overview |
176
+
177
+ ### Intents & Runtime
178
+
179
+ Intents are declarative execution orders. You define the trade and trigger condition; the runtime daemon evaluates and executes.
180
+
181
+ | Command | Description |
182
+ |---------|-------------|
183
+ | `sf intent buy <ticker> <qty>` | Create buy intent (`--trigger below:28`, `--auto`, `--expire 1d`) |
184
+ | `sf intent sell <ticker> <qty>` | Create sell intent |
185
+ | `sf intent list` | Active intents (`--all` for history) |
186
+ | `sf intent status <id>` | Detailed status + fills |
187
+ | `sf intent cancel <id>` | Cancel an intent |
188
+ | `sf runtime start` | Start execution daemon |
189
+ | `sf runtime stop` | Stop daemon |
190
+ | `sf runtime status` | Active intents + runtime state |
82
191
 
83
- ### Trading (requires `sf setup --enable-trading`)
192
+ ### Trading (requires `--enable-trading`)
84
193
 
85
194
  | Command | Description |
86
195
  |---------|-------------|
87
- | `sf buy <ticker> <qty>` | Buy contracts |
196
+ | `sf buy <ticker> <qty>` | Buy contracts (`--price 31`, `--market`, `--side yes\|no`) |
88
197
  | `sf sell <ticker> <qty>` | Sell contracts |
89
198
  | `sf cancel [orderId]` | Cancel order(s) |
90
199
  | `sf rfq <ticker> <qty>` | Request for quote on large orders |
91
200
 
92
- ### Analysis
201
+ ### Agent
93
202
 
94
203
  | Command | Description |
95
204
  |---------|-------------|
96
- | `sf whatif <id>` | What-if scenario: "if node X drops to 10%..." |
97
- | `sf feed` | Evaluation history stream |
98
- | `sf forecast <event>` | Market distribution forecast (P50/P75/P90) |
99
- | `sf dashboard` | Interactive TUI portfolio overview |
205
+ | `sf agent [id]` | Interactive agent (TUI with panels) |
206
+ | `sf agent [id] --plain` | Readline mode, pipe-friendly |
207
+ | `sf agent [id] --once "prompt"` | Single-shot: run, answer, exit |
208
+ | `sf prompt [id]` | Print dynamic system prompt for any LLM |
209
+ | `sf telegram [--daemon]` | Telegram bot (text + voice messages) |
210
+
211
+ The agent has 30+ tools including market scanning, trading, workspace read/write, X search, self-wake scheduling, and a math calculator. Slash commands inside the agent: `/voice` `/tree` `/edges` `/pos` `/eval` `/balance` `/intents` `/wakes` `/alerts` `/switch` `/model` `/help` `/exit`.
100
212
 
101
- ### Interactive Modes
213
+ ### X / Twitter
102
214
 
103
215
  | Command | Description |
104
216
  |---------|-------------|
105
- | `sf agent [id]` | Interactive agent with natural language + tool calling |
106
- | `sf telegram` | Telegram bot for monitoring and trading |
217
+ | `sf x "query"` | Discussion summary + top posts |
218
+ | `sf x-volume "query"` | Discussion volume and velocity trend |
219
+ | `sf x-news "query"` | Grok-aggregated news stories |
220
+ | `sf x-account @username` | Recent posts from a specific account |
221
+
222
+ ### Info
223
+
224
+ | Command | Description |
225
+ |---------|-------------|
226
+ | `sf feed` | Evaluation history stream |
227
+ | `sf delta <id>` | Changes since last check |
228
+ | `sf milestones` | Upcoming Kalshi events |
229
+ | `sf schedule` | Exchange status |
230
+ | `sf announcements` | Exchange announcements |
231
+ | `sf history <ticker>` | Historical market data |
232
+ | `sf status` | Full system health check |
233
+
234
+ ### Sharing
235
+
236
+ Any command with `--share` generates a short URL:
237
+
238
+ ```bash
239
+ sf scan "gold" --share
240
+ sf query "fed rate" --share
241
+ sf context --share
242
+ ```
107
243
 
108
244
  ## JSON Output
109
245
 
@@ -113,139 +249,121 @@ Most commands support `--json` for machine-readable output:
113
249
  sf list --json
114
250
  sf context f582bf76 --json
115
251
  sf edges --json
116
- sf scan "oil" --json
117
- sf get f582bf76 --json
252
+ sf world --json
253
+ sf ideas --json
254
+ sf intent list --json
118
255
  ```
119
256
 
120
257
  ## For AI Agents
121
258
 
122
- **If you are an AI coding agent (Claude, GPT, OpenClaw, Cursor, etc.), follow these steps:**
259
+ If you are an AI agent (Claude, GPT, Cursor, etc.), these are the commands that matter:
123
260
 
124
- ### Step 1: Install and configure
261
+ ### Setup
125
262
 
126
263
  ```bash
127
264
  npm install -g @spfunctions/cli
128
- sf setup --key sf_live_YOUR_KEY_HERE
265
+ sf login # or: export SF_API_KEY=sf_live_xxx
266
+ sf list # verify: should print theses
129
267
  ```
130
268
 
131
- If `sf setup` is not available or you cannot run interactive commands:
132
- ```bash
133
- export SF_API_KEY=sf_live_YOUR_KEY_HERE
134
- ```
135
-
136
- ### Step 2: Verify it works
269
+ ### Primary commands
137
270
 
138
271
  ```bash
139
- sf list
140
- ```
272
+ # World state in ~800 tokens — give your agent situational awareness
273
+ sf world --json
141
274
 
142
- Expected output (table format):
143
- ```
144
- ID Status Conf Updated Title
145
- f582bf76 active 82% Mar 12 11:13 Oil prices will exceed $100...
146
- a1b2c3d4 active 65% Mar 11 09:30 Fed will cut rates by June...
147
- ```
275
+ # What changed since last poll — ~30 tokens, efficient for loops
276
+ sf world --delta --json
148
277
 
149
- If you see theses, the CLI is working. If you see an error, check that `SF_API_KEY` is set correctly.
278
+ # Thesis context: causal tree + edges + positions + evaluation
279
+ sf context <thesisId> --json
150
280
 
151
- ### Step 3: Get thesis context (most important command)
281
+ # Top mispricings across all theses
282
+ sf edges --json
152
283
 
153
- ```bash
154
- sf context <thesisId> --json
284
+ # Single-shot agent: ask a question, get an answer, exit
285
+ sf agent <thesisId> --once "what is the biggest risk to my portfolio?"
155
286
  ```
156
287
 
157
- This returns a JSON object with the complete thesis state:
288
+ ### Key JSON fields
289
+
290
+ `sf context <id> --json` returns:
158
291
 
159
292
  ```json
160
293
  {
161
- "thesisId": "f582bf76-3113-4208-b0c1-...",
162
- "thesis": "Oil prices will exceed $100 by end of 2026",
163
- "title": "Oil Bull Thesis",
164
- "status": "active",
165
- "confidence": 0.82,
294
+ "confidence": 0.31,
166
295
  "causalTree": {
167
- "rootClaim": "Oil prices will exceed $100",
168
- "nodes": [
169
- {
170
- "id": "n1",
171
- "label": "Supply disruption",
172
- "probability": 0.75,
173
- "importance": 0.6,
174
- "depth": 0
175
- }
176
- ]
296
+ "nodes": [{ "id": "n1", "label": "...", "probability": 0.95 }]
177
297
  },
178
- "edges": [
179
- {
180
- "marketId": "KXWTIMAX-26DEC31-T100",
181
- "market": "Will oil exceed $100 by Dec 2026?",
182
- "venue": "kalshi",
183
- "direction": "yes",
184
- "marketPrice": 35,
185
- "thesisPrice": 55,
186
- "edge": 20,
187
- "confidence": 0.8
188
- }
189
- ],
190
- "lastEvaluation": {
191
- "summary": "Supply concerns rising due to...",
192
- "newConfidence": 0.82,
193
- "confidenceDelta": 0.03
194
- }
298
+ "edges": [{
299
+ "marketId": "KXWTIMAX-26DEC31-T140",
300
+ "venue": "kalshi",
301
+ "marketPrice": 62,
302
+ "thesisPrice": 95,
303
+ "edge": 33
304
+ }]
195
305
  }
196
306
  ```
197
307
 
198
- **Key fields:**
199
308
  - `confidence` — overall thesis probability (0 to 1)
200
- - `edges[].edge` — mispricing size in cents (positive = market underpriced vs thesis)
201
- - `edges[].marketPrice` — current market price in cents (0-100)
202
- - `edges[].thesisPrice` — what the thesis model thinks the price should be
203
- - `lastEvaluation.summary` — human-readable summary of latest analysis
309
+ - `edges[].edge` — mispricing in cents (positive = market underpriced vs model)
310
+ - `edges[].marketPrice` — current market price (0-100 cents)
204
311
 
205
- ### Step 4: Other useful commands
312
+ ### Patterns
206
313
 
207
314
  ```bash
208
- # Inject a signal for the thesis to consider in its next evaluation
209
- sf signal <thesisId> "Breaking: OPEC announces production cut" --type news
210
-
211
- # View top edges (mispricings) across all theses
212
- sf edges --json
213
-
214
- # Search Kalshi markets by keyword
215
- sf scan "recession" --json
315
+ # Monitor: poll world state, react to deltas
316
+ sf world --delta --json
216
317
 
217
- # Trigger a deep re-evaluation
218
- sf evaluate <thesisId>
318
+ # React to news: inject signal, re-evaluate, read updated state
319
+ sf signal <id> "Breaking: OPEC cuts production" --type news
320
+ sf evaluate <id>
321
+ sf context <id> --json
219
322
 
220
- # What-if analysis: what happens if a node probability changes?
221
- sf whatif <thesisId>
323
+ # Trade: create intent with trigger, let runtime execute
324
+ sf intent buy KXRECSSNBER-26 5 --price 30 --trigger below:28 --auto
325
+ sf runtime start
222
326
  ```
223
327
 
224
- ### Common patterns for agents
328
+ ### Error codes
225
329
 
226
- **Monitor a thesis:**
227
- ```bash
228
- sf context <id> --json # poll periodically, check confidence changes
229
- ```
330
+ | Error | Fix |
331
+ |-------|-----|
332
+ | "API key required" | `sf login` or set `SF_API_KEY` |
333
+ | "Thesis not found" | `sf list` to get valid IDs (8-char prefix works) |
334
+ | "Kalshi not configured" | `sf setup` to add Kalshi credentials |
335
+ | "Trading disabled" | `sf setup --enable-trading` |
230
336
 
231
- **React to news:**
232
- ```bash
233
- sf signal <id> "Reuters: Iran nuclear deal collapses" --type news
234
- sf evaluate <id> # trigger re-evaluation after injecting signal
235
- sf context <id> --json # read updated state
236
- ```
337
+ ## Architecture
237
338
 
238
- **Find trading opportunities:**
239
- ```bash
240
- sf edges --json # get top mispricings sorted by edge size
339
+ ```
340
+ You ──→ sf agent ──→ LLM (Claude/GPT) ──→ 30+ tools
341
+ │ │
342
+ │ ┌────────────────────────┘
343
+ ▼ ▼
344
+ ┌─────────────────────┐
345
+ │ SF API Server │
346
+ │ thesis · edges · │
347
+ │ intents · eval · │
348
+ │ proxy (LLM/TTS) │
349
+ └────────┬────────────┘
350
+
351
+ ┌────────┴────────┐
352
+ │ │
353
+ ┌────▼────┐ ┌──────▼──────┐
354
+ │ Kalshi │ │ Polymarket │
355
+ │ API │ │ API │
356
+ └─────────┘ └─────────────┘
357
+
358
+ Local only (never leaves your machine):
359
+ ~/.sf/config.json — API keys, Kalshi private key path
360
+ ~/.sf/workspace/ — agent scratchpad (notes, context logs)
361
+ ~/.sf/wake/ — scheduled agent wake conditions
362
+ ~/.sf/bus/ — daemon ↔ agent message bus
363
+ ~/.sf/trade-journal.jsonl — local trade log
241
364
  ```
242
365
 
243
- ### Error handling
244
-
245
- - **"API key required"** — set `SF_API_KEY` env var or run `sf setup --key <key>`
246
- - **"Thesis not found"** — use `sf list` to get valid thesis IDs. IDs can be short prefixes (first 8 chars)
247
- - **"Kalshi not configured"** — positions/trading commands need Kalshi credentials via `sf setup`
248
- - **Exit code 0** — success. **Exit code 1** — error (message printed to stderr)
366
+ **Kalshi credentials never leave your machine.** Orders are signed locally with your RSA private key and sent directly to the Kalshi API. The SF server never sees your trading keys.
249
367
 
250
368
  ## Local Development
251
369
 
@@ -254,6 +372,10 @@ cd cli
254
372
  npm install
255
373
  npm run dev -- list # run without building
256
374
  npm run build # compile to dist/
257
- npm run test # run unit tests
375
+ npm run test # run tests
258
376
  npm link # install as global 'sf' command
259
377
  ```
378
+
379
+ ## License
380
+
381
+ MIT
package/dist/12.index.js CHANGED
@@ -1 +1 @@
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,u.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,u.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{const n=setInterval(()=>{e.replyWithChatAction("typing").catch(()=>{})},4e3);await e.replyWithChatAction("typing");const{runAgentMessage:r}=await s.e(160).then(s.bind(s,28160)),i=new Promise((e,t)=>setTimeout(()=>t(new Error("Response timeout (30s)")),3e4));let c;try{c=await Promise.race([r(o,t,a),i])}finally{clearInterval(n)}if(c&&0!==c.trim().length)for(const t of(0,d.splitMessage)(c))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{r.default.unlinkSync(p)}catch{}}console.log(" Press Ctrl+C to stop.\n"),r.default.mkdirSync(i.default.dirname(p),{recursive:!0}),r.default.writeFileSync(p,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{r.default.unlinkSync(p)}catch{}});for(let e=0;e<=5;e++)try{await f.start({onStart:()=>console.log("[Telegram] Polling started successfully")});break}catch(t){if((t?.message?.includes("409")||409===t?.error_code)&&e<5){const t=5*(e+1);console.error(`[Telegram] 409 conflict — retrying in ${t}s (${e+1}/5)`),await new Promise(e=>setTimeout(e,1e3*t));continue}console.error(`[Telegram] Fatal: ${t.message}`),$(),process.exit(1)}};const n=s(53278),r=a(s(79896)),i=a(s(16928)),o=a(s(70857)),c=s(19218),l=s(11627),d=s(76342),h=s(77499),u=s(55875),p=i.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):"?",r=s.thesis||s.rawThesis||"N/A";let i=`📋 <b>${(0,n.escapeHtml)(r.slice(0,80))}</b>\n`;i+=`Confidence: <b>${a}%</b> | Status: ${s.status}\n\n`;const o=(s.edges||[]).slice(0,8);if(o.length>0){i+="<b>Top Edges:</b>\n<pre>";for(const e of o){i+=`${(e.market||e.marketTitle||"???").slice(0,35).padEnd(36)} ${String(e.edge||0).padStart(3)}¢ ${e.direction||"yes"}\n`}i+="</pre>"}return i},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 r of e){const e=await(0,a.getMarketPrice)(r.ticker)??r.average_price_paid,i=(e-r.average_price_paid)*r.quantity;t+=i;const o=(0,n.fmtDollar)(i);s.push(`${r.ticker.slice(0,28).padEnd(29)} ${String(r.quantity).padStart(5)} ${String(r.average_price_paid).padStart(3)}¢→${String(e).padStart(3)}¢ ${o}`)}let r="📊 <b>Positions</b>\n<pre>";return r+=s.join("\n"),r+=`\n${"─".repeat(50)}\nTotal P&L: ${(0,n.fmtDollar)(t)}`,r+="</pre>",r},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 r=n.slice(0,10);if(0===r.length)return"No edges found.";let i="📈 <b>Top Edges</b>\n<pre>";for(const e of r){const t=(e.market||"???").slice(0,35),s=e.orderbook?.liquidityScore||"?";i+=`${t.padEnd(36)} +${String(e.edge||0).padStart(2)}¢ ${s}\n`}return i+="</pre>",i},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 r=(new Date).toISOString();return setInterval(async()=>{try{const i=await s.getChanges(n,r);if(r=(new Date).toISOString(),!i||!i.changed)return;const o=i.confidenceDelta??i.delta??0;if(Math.abs(o)<.02)return;const c="number"==typeof i.confidence?Math.round(100*i.confidence):"?",l=o>0?"📈":"📉",d=o>0?"+":"",h=i.summary||i.latestSummary||"";let u=`${l} <b>Confidence ${d}${Math.round(100*o)}% → ${c}%</b>\n`;h&&(u+=`${(0,a.escapeHtml)(h.slice(0,200))}`),await e.api.sendMessage(t,u,{parse_mode:"HTML"})}catch{}},6e4)};const a=s(76342)}};
1
+ "use strict";exports.id=12,exports.ids=[12],exports.modules={6012:function(e,t,a){var s=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)(),s=e.token||t.telegramBotToken||process.env.TELEGRAM_BOT_TOKEN;s||(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 i.Bot(s),y=e.chatId||null,m=new Map;function w(e){return!y||e===y}async function $(e,t){const a=g(e);a.thesisId=t.id,a.agentMessages=[],a.agent=void 0,m.has(e)&&clearInterval(m.get(e)),m.set(e,(0,p.startPoller)(f,e,o,a.thesisId))}f.command("start",async e=>{if(!w(e.chat.id))return;g(e.chat.id);try{const{theses:t}=await o.listTheses(),a=(t||[]).filter(e=>"active"===e.status);if(0===a.length)return void await e.reply('No active theses found. Create one with `sf create "your thesis"`');if(1===a.length)await $(e.chat.id,a[0]),await e.reply(`✅ Connected\n\nThesis: <b>${(a[0].rawThesis||"").slice(0,80)}</b>\nID: <code>${a[0].id.slice(0,8)}</code>\n\nType naturally, send voice, or use /help`,{parse_mode:"HTML"});else{const t=new i.InlineKeyboard;for(const e of a.slice(0,8)){const a=`${(e.rawThesis||e.id).slice(0,40)}`;t.text(a,`pick_thesis:${e.id}`).row()}await e.reply(`✅ Connected — ${a.length} active theses\n\nPick one:`,{parse_mode:"HTML",reply_markup:t})}}catch(t){await e.reply(`❌ Connection failed: ${t.message}\nCheck SF_API_KEY in ~/.sf/config.json`)}}),f.on("callback_query:data",async e=>{const n=e.callbackQuery.data;if(n.startsWith("pick_thesis:")){const t=n.slice(12);try{const{theses:a}=await o.listTheses(),s=(a||[]).find(e=>e.id===t);if(!s)return void await e.answerCallbackQuery({text:"Thesis not found"});await $(e.chat.id,s),await e.answerCallbackQuery({text:`Selected: ${(s.rawThesis||"").slice(0,30)}`}),await e.editMessageText(`✅ Active: <b>${(s.rawThesis||"").slice(0,80)}</b>\nID: <code>${s.id.slice(0,8)}</code>\n\nType naturally, send voice, or use /help`,{parse_mode:"HTML"})}catch(t){await e.answerCallbackQuery({text:`Error: ${t.message}`})}return}f.command("context",async e=>{if(!w(e.chat.id))return;const t=g(e.chat.id);if(t.thesisId)try{const a=await(0,h.handleContext)(o,t.thesisId);for(const t of(0,d.splitMessage)(a))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 a of(0,d.splitMessage)(t))await e.reply(a,{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 a of(0,d.splitMessage)(t))await e.reply(a,{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 a of(0,d.splitMessage)(t))await e.reply(a,{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 a=await(0,h.handleEval)(o,t.thesisId);await e.reply(a)}catch(t){await e.reply(`❌ ${t.message}`)}else await e.reply("No thesis selected.")}),f.command("help",async e=>{w(e.chat.id)&&await e.reply("<b>Commands</b>\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 &lt;id&gt; — switch thesis\n\n🎤 Send a voice message to talk\n💬 Or just type naturally",{parse_mode:"HTML"})}),f.command("list",async e=>{if(w(e.chat.id))try{const t=await(0,h.handleList)(o);for(const a of(0,d.splitMessage)(t))await e.reply(a,{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),a=e.match?.trim();if(a)try{const{theses:s}=await o.listTheses(),i=(s||[]).find(e=>e.id.startsWith(a));i?(t.thesisId=i.id,t.agentMessages=[],t.agent=void 0,m.has(e.chat.id)&&clearInterval(m.get(e.chat.id)),m.set(e.chat.id,(0,p.startPoller)(f,e.chat.id,o,t.thesisId)),await e.reply(`Switched to: <code>${i.id.slice(0,8)}</code> — ${(i.rawThesis||"").slice(0,60)}`,{parse_mode:"HTML"})):await e.reply(`No thesis found matching "${a}". 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),s=e.message.text;if(!s.startsWith("/")){if(!t.thesisId)try{const{theses:a}=await o.listTheses(),s=(a||[]).filter(e=>"active"===e.status);if(!(s.length>0))return void await e.reply('No active theses. Create one with `sf create "your thesis"` then /start');t.thesisId=s[0].id}catch{return void await e.reply("Could not connect. Use /start first.")}try{const i=await e.reply("⏳ Thinking..."),n=[];let r=0;const c=setInterval(()=>{e.replyWithChatAction("typing").catch(()=>{})},4e3);await e.replyWithChatAction("typing");const{runAgentMessage:l}=await a.e(160).then(a.bind(a,28160)),h=new Promise((e,t)=>setTimeout(()=>t(new Error("Response timeout (30s)")),3e4)),p=t=>{n.push(t);const a=Date.now();if(a-r>1e3){r=a;const t=n.map(e=>`⚡ ${e}`).join(" → ");e.api.editMessageText(e.chat.id,i.message_id,`⏳ ${t}...`).catch(()=>{})}};let u;try{u=await Promise.race([l(o,t,s,p),h])}finally{clearInterval(c)}try{await e.api.deleteMessage(e.chat.id,i.message_id)}catch{}if(u&&0!==u.trim().length)for(const t of(0,d.splitMessage)(u))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(["message:voice","message:audio"],async e=>{if(!w(e.chat.id))return;const n=g(e.chat.id);if(!n.thesisId)try{const{theses:t}=await o.listTheses(),a=(t||[]).filter(e=>"active"===e.status);if(!(a.length>0))return void await e.reply("No active theses. Create one first.");n.thesisId=a[0].id}catch{return void await e.reply("Could not connect. Use /start first.")}try{await e.replyWithChatAction("typing");const r=e.message.voice||e.message.audio;if(!r)return void await e.reply("Could not read audio.");const c=await e.api.getFile(r.file_id);if(!c.file_path)return void await e.reply("Could not download audio.");const l=`https://api.telegram.org/file/bot${s}/${c.file_path}`,h=await fetch(l);if(!h.ok)return void await e.reply("Failed to download voice message.");const p=Buffer.from(await h.arrayBuffer()),u=t.apiKey||process.env.SF_API_KEY,f=t.apiUrl||process.env.SF_API_URL||"https://simplefunctions.dev";if(!u)return void await e.reply("SF API key required for voice transcription.");const g=new Blob([p],{type:"audio/ogg"}),y=new FormData;y.append("audio",g,"voice.ogg");const m=await fetch(`${f}/api/proxy/stt`,{method:"POST",headers:{Authorization:`Bearer ${u}`},body:y,signal:AbortSignal.timeout(3e4)});if(!m.ok)return void await e.reply("Transcription failed.");const w=(await m.json()).text;if(!w||0===w.trim().length)return void await e.reply("Could not transcribe voice message.");const $=await e.reply(`🎤 "${w}"\n⏳ Thinking...`),T=[];let b=0;const _=setInterval(()=>{e.replyWithChatAction("typing").catch(()=>{})},4e3);await e.replyWithChatAction("typing");const{runAgentMessage:v}=await a.e(160).then(a.bind(a,28160)),M=t=>{T.push(t);const a=Date.now();if(a-b>1e3){b=a;const t=T.map(e=>`⚡ ${e}`).join(" → ");e.api.editMessageText(e.chat.id,$.message_id,`🎤 "${w}"\n⏳ ${t}...`).catch(()=>{})}};let S;try{S=await Promise.race([v(o,n,w,M),new Promise((e,t)=>setTimeout(()=>t(new Error("Timeout")),3e4))])}finally{clearInterval(_)}try{await e.api.editMessageText(e.chat.id,$.message_id,`🎤 "${w}"`)}catch{}if(S&&S.trim().length>0){for(const t of(0,d.splitMessage)(S))try{await e.reply(t,{parse_mode:"HTML"})}catch{await e.reply(t)}const t=S.replace(/<[^>]*>/g,"").trim();if(t.length>0&&t.length<500)try{const a=await fetch(`${f}/api/proxy/tts`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify({text:t.slice(0,500)}),signal:AbortSignal.timeout(15e3)});if(a.ok){const t=Buffer.from(await a.arrayBuffer());await e.replyWithVoice(new i.InputFile(t,"reply.mp3"))}}catch{}}}catch(t){console.error("[Telegram] Voice error:",t.message),await e.reply(`❌ Voice: ${t.message}`)}}),n.startsWith("order_confirm:")?await e.answerCallbackQuery({text:"Order execution coming soon"}):"order_cancel"===n&&(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?"✓":"✗")),y&&console.log(` Restricted to chat: ${y}`);function T(){for(const e of m.values())clearInterval(e);f.stop();try{n.default.unlinkSync(u)}catch{}}console.log(" Press Ctrl+C to stop.\n"),n.default.mkdirSync(r.default.dirname(u),{recursive:!0}),n.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",()=>{T(),process.exit(0)}),process.on("SIGTERM",()=>{T(),process.exit(0)}),process.on("exit",()=>{try{n.default.unlinkSync(u)}catch{}});for(let e=0;e<=5;e++)try{await f.start({onStart:()=>console.log("[Telegram] Polling started successfully")});break}catch(t){if((t?.message?.includes("409")||409===t?.error_code)&&e<5){const t=5*(e+1);console.error(`[Telegram] 409 conflict — retrying in ${t}s (${e+1}/5)`),await new Promise(e=>setTimeout(e,1e3*t));continue}console.error(`[Telegram] Fatal: ${t.message}`),T(),process.exit(1)}};const i=a(53278),n=s(a(79896)),r=s(a(16928)),o=s(a(70857)),c=a(19218),l=a(11627),d=a(76342),h=a(77499),p=a(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,a)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.handleContext=async function(e,t){const a=await e.getContext(t),s="number"==typeof a.confidence?Math.round(100*a.confidence):"?",n=a.thesis||a.rawThesis||"N/A";let r=`📋 <b>${(0,i.escapeHtml)(n.slice(0,80))}</b>\n`;r+=`Confidence: <b>${s}%</b> | Status: ${a.status}\n\n`;const o=(a.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,s.isKalshiConfigured)())return"⚠️ Kalshi not configured. Run <code>sf setup --kalshi</code>";const e=await(0,s.getPositions)();if(!e||0===e.length)return"No open positions.";let t=0;const a=[];for(const n of e){const e=await(0,s.getMarketPrice)(n.ticker)??n.average_price_paid,r=(e-n.average_price_paid)*n.quantity;t+=r;const o=(0,i.fmtDollar)(r);a.push(`${n.ticker.slice(0,28).padEnd(29)} ${String(n.quantity).padStart(5)} ${String(n.average_price_paid).padStart(3)}¢→${String(e).padStart(3)}¢ ${o}`)}let n="📊 <b>Positions</b>\n<pre>";return n+=a.join("\n"),n+=`\n${"─".repeat(50)}\nTotal P&L: ${(0,i.fmtDollar)(t)}`,n+="</pre>",n},t.handleEdges=async function(e){const{theses:t}=await e.listTheses(),a=(t||[]).filter(e=>"active"===e.status),s=await Promise.allSettled(a.map(async t=>((await e.getContext(t.id)).edges||[]).map(e=>({...e,thesisId:t.id})))),i=[];for(const e of s)"fulfilled"===e.status&&i.push(...e.value);i.sort((e,t)=>Math.abs(t.edge||0)-Math.abs(e.edge||0));const n=i.slice(0,10);if(0===n.length)return"No edges found.";let r="📈 <b>Top Edges</b>\n<pre>";for(const e of n){const t=(e.market||"???").slice(0,35),a=e.orderbook?.liquidityScore||"?";r+=`${t.padEnd(36)} +${String(e.edge||0).padStart(2)}¢ ${a}\n`}return r+="</pre>",r},t.handleBalance=async function(){if(!(0,s.isKalshiConfigured)())return"⚠️ Kalshi not configured.";const e=await(0,s.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,s.isKalshiConfigured)())return"⚠️ Kalshi not configured.";const e=await(0,s.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 a of e.orders){const e=Math.round(parseFloat(a.remaining_count_fp||"0")),s=Math.round(100*parseFloat(a.yes_price_dollars||"0")),i=(a.ticker||"???").slice(0,25);t+=`${a.action} ${e}x ${i} @ ${s}¢\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 a="📝 <b>Theses</b>\n<pre>";for(const e of t){const t="number"==typeof e.confidence?Math.round(100*e.confidence):0,s=(e.rawThesis||e.thesis||"").slice(0,50);a+=`${e.id.slice(0,8)} ${String(t).padStart(3)}% ${e.status.padEnd(8)} ${s}\n`}return a+="</pre>",a};const s=a(96139),i=a(76342)},76342:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.splitMessage=function(e,t=4e3){if(e.length<=t)return[e];const a=[];let s=e;for(;s.length>0;){if(s.length<=t){a.push(s);break}let e=s.lastIndexOf("\n",t);e<.5*t&&(e=t),a.push(s.slice(0,e)),s=s.slice(e)}return a},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),a=Math.max(...e)-t||1;return e.map(e=>"▁▂▃▄▅▆▇█"[Math.round((e-t)/a*7)]).join("")},t.escapeHtml=function(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}},55875:(e,t,a)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.startPoller=function(e,t,a,i){let n=(new Date).toISOString();return setInterval(async()=>{try{const r=await a.getChanges(i,n);if(n=(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,s.escapeHtml)(h.slice(0,200))}`),await e.api.sendMessage(t,p,{parse_mode:"HTML"})}catch{}},6e4)};const s=a(76342)}};