@spfunctions/cli 1.4.5 → 1.5.1

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.
@@ -14,6 +14,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.positionsCommand = positionsCommand;
15
15
  const client_js_1 = require("../client.js");
16
16
  const kalshi_js_1 = require("../kalshi.js");
17
+ const polymarket_js_1 = require("../polymarket.js");
18
+ const config_js_1 = require("../config.js");
17
19
  const utils_js_1 = require("../utils.js");
18
20
  async function positionsCommand(opts) {
19
21
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
@@ -23,6 +25,18 @@ async function positionsCommand(opts) {
23
25
  console.log(`${utils_js_1.c.dim}Fetching Kalshi positions...${utils_js_1.c.reset}`);
24
26
  positions = await (0, kalshi_js_1.getPositions)();
25
27
  }
28
+ // ── Step 1b: Fetch Polymarket positions (if wallet configured) ──
29
+ const config = (0, config_js_1.loadConfig)();
30
+ let polyPositions = [];
31
+ if (config.polymarketWalletAddress) {
32
+ console.log(`${utils_js_1.c.dim}Fetching Polymarket positions...${utils_js_1.c.reset}`);
33
+ try {
34
+ polyPositions = await (0, polymarket_js_1.polymarketGetPositions)(config.polymarketWalletAddress);
35
+ }
36
+ catch {
37
+ // skip
38
+ }
39
+ }
26
40
  // ── Step 2: Fetch all theses and their edges (via SF API) ──
27
41
  console.log(`${utils_js_1.c.dim}Fetching thesis edges...${utils_js_1.c.reset}`);
28
42
  let theses = [];
@@ -100,7 +114,9 @@ async function positionsCommand(opts) {
100
114
  if (opts.json) {
101
115
  console.log(JSON.stringify({
102
116
  kalshiConfigured: (0, kalshi_js_1.isKalshiConfigured)(),
117
+ polymarketConfigured: !!config.polymarketWalletAddress,
103
118
  positions: positions || [],
119
+ polymarketPositions: polyPositions,
104
120
  edges: allEdges.map(e => ({ ...e.edge, thesisId: e.thesisId })),
105
121
  }, null, 2));
106
122
  return;
@@ -169,6 +185,40 @@ async function positionsCommand(opts) {
169
185
  else {
170
186
  console.log(`\n${utils_js_1.c.yellow}Kalshi not configured.${utils_js_1.c.reset} Set KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY_PATH to see positions.\n`);
171
187
  }
188
+ // C) Polymarket positions
189
+ if (polyPositions.length > 0) {
190
+ (0, utils_js_1.header)('Polymarket Positions');
191
+ console.log(' ' + utils_js_1.c.bold +
192
+ (0, utils_js_1.pad)('Market', 35) +
193
+ (0, utils_js_1.rpad)('Side', 5) +
194
+ (0, utils_js_1.rpad)('Size', 8) +
195
+ (0, utils_js_1.rpad)('Avg', 6) +
196
+ (0, utils_js_1.rpad)('Now', 6) +
197
+ (0, utils_js_1.rpad)('P&L', 9) +
198
+ utils_js_1.c.reset);
199
+ console.log(' ' + utils_js_1.c.dim + '─'.repeat(75) + utils_js_1.c.reset);
200
+ for (const pos of polyPositions) {
201
+ const title = (pos.title || pos.slug || pos.asset || '').slice(0, 34);
202
+ const side = pos.outcome || 'YES';
203
+ const size = pos.size || 0;
204
+ const avgPrice = Math.round((pos.avgPrice || 0) * 100);
205
+ const curPrice = Math.round((pos.curPrice || pos.currentPrice || 0) * 100);
206
+ const pnl = pos.cashPnl || ((curPrice - avgPrice) * size / 100);
207
+ const pnlColor = pnl >= 0 ? utils_js_1.c.green : utils_js_1.c.red;
208
+ const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
209
+ console.log(' ' +
210
+ (0, utils_js_1.pad)(title, 35) +
211
+ (0, utils_js_1.rpad)(side.toUpperCase(), 5) +
212
+ (0, utils_js_1.rpad)(String(Math.round(size)), 8) +
213
+ (0, utils_js_1.rpad)(`${avgPrice}¢`, 6) +
214
+ (0, utils_js_1.rpad)(`${curPrice}¢`, 6) +
215
+ `${pnlColor}${(0, utils_js_1.rpad)(pnlStr, 9)}${utils_js_1.c.reset}`);
216
+ }
217
+ console.log('');
218
+ }
219
+ else if (config.polymarketWalletAddress) {
220
+ console.log(`${utils_js_1.c.dim}No open positions on Polymarket.${utils_js_1.c.reset}\n`);
221
+ }
172
222
  // B) Unpositioned edges (edges without matching positions)
173
223
  const positionedTickers = new Set((positions || []).map(p => p.ticker));
174
224
  const unpositionedEdges = allEdges.filter(e => !positionedTickers.has(e.edge.marketId));
@@ -1,6 +1,7 @@
1
1
  interface ScanOpts {
2
2
  series?: string;
3
3
  market?: string;
4
+ venue?: string;
4
5
  json?: boolean;
5
6
  apiKey?: string;
6
7
  apiUrl?: string;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanCommand = scanCommand;
4
4
  const client_js_1 = require("../client.js");
5
+ const polymarket_js_1 = require("../polymarket.js");
5
6
  const utils_js_1 = require("../utils.js");
6
7
  async function scanCommand(query, opts) {
7
8
  // Mode 1: --market TICKER — single market detail
@@ -14,8 +15,8 @@ async function scanCommand(query, opts) {
14
15
  await showSeries(opts.series.toUpperCase(), opts.json);
15
16
  return;
16
17
  }
17
- // Mode 3: keyword scan across all series
18
- await keywordScan(query, opts.json);
18
+ // Mode 3: keyword scan across all series + Polymarket
19
+ await keywordScan(query, opts.json, opts.venue || 'all');
19
20
  }
20
21
  async function showMarket(ticker, json) {
21
22
  console.log(`${utils_js_1.c.dim}Fetching market ${ticker}...${utils_js_1.c.reset}`);
@@ -77,10 +78,19 @@ async function showSeries(seriesTicker, json) {
77
78
  printMarketsTable(markets);
78
79
  }
79
80
  }
80
- async function keywordScan(query, json) {
81
+ async function keywordScan(query, json, venue = 'all') {
81
82
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
82
- console.log(`${utils_js_1.c.dim}Scanning Kalshi for: "${query}"...${utils_js_1.c.reset}`);
83
- const allSeries = await (0, client_js_1.kalshiFetchAllSeries)();
83
+ // ── Polymarket search (runs in parallel with Kalshi) ───────────────────────
84
+ const polyPromise = (venue === 'kalshi')
85
+ ? Promise.resolve([])
86
+ : (0, polymarket_js_1.polymarketSearch)(query, 10).catch(() => []);
87
+ if (venue !== 'polymarket') {
88
+ console.log(`${utils_js_1.c.dim}Scanning Kalshi for: "${query}"...${utils_js_1.c.reset}`);
89
+ }
90
+ if (venue !== 'kalshi') {
91
+ console.log(`${utils_js_1.c.dim}Scanning Polymarket for: "${query}"...${utils_js_1.c.reset}`);
92
+ }
93
+ const allSeries = venue === 'polymarket' ? [] : await (0, client_js_1.kalshiFetchAllSeries)();
84
94
  // Score each series
85
95
  const thesisKeywords = [
86
96
  'oil', 'wti', 'gas', 'recession', 'gdp', 'fed', 'inflation',
@@ -126,13 +136,14 @@ async function keywordScan(query, json) {
126
136
  for (const m of markets) {
127
137
  if (m.status === 'open' || m.status === 'active') {
128
138
  allMarkets.push({
139
+ venue: 'kalshi',
129
140
  seriesTicker: s.ticker,
130
141
  ticker: m.ticker,
131
142
  title: m.title || m.subtitle || '',
132
143
  yesAsk: parseFloat(m.yes_ask_dollars || '0'),
133
144
  lastPrice: parseFloat(m.last_price_dollars || '0'),
134
145
  volume24h: parseFloat(m.volume_24h_fp || '0'),
135
- liquidity: parseFloat(m.liquidity_dollars || '0'),
146
+ liquidity: parseFloat(m.open_interest_fp || m.liquidity_dollars || '0'),
136
147
  });
137
148
  }
138
149
  }
@@ -144,27 +155,67 @@ async function keywordScan(query, json) {
144
155
  }
145
156
  }
146
157
  allMarkets.sort((a, b) => b.liquidity - a.liquidity);
158
+ // ── Polymarket results ──────────────────────────────────────────────────────
159
+ const polyEvents = await polyPromise;
160
+ const polyMarkets = [];
161
+ for (const event of polyEvents) {
162
+ for (const m of (event.markets || [])) {
163
+ if (!m.active || m.closed)
164
+ continue;
165
+ const prices = (0, polymarket_js_1.parseOutcomePrices)(m.outcomePrices);
166
+ const yesPrice = prices[0] || 0;
167
+ polyMarkets.push({
168
+ venue: 'polymarket',
169
+ ticker: m.conditionId?.slice(0, 16) || m.id,
170
+ title: m.groupItemTitle
171
+ ? `${event.title}: ${m.groupItemTitle}`
172
+ : m.question || event.title,
173
+ yesAsk: yesPrice,
174
+ lastPrice: m.lastTradePrice || yesPrice,
175
+ volume24h: m.volume24hr || 0,
176
+ liquidity: m.liquidityNum || 0,
177
+ });
178
+ }
179
+ }
180
+ // ── Merge & output ─────────────────────────────────────────────────────────
181
+ // Tag Kalshi markets with venue
182
+ for (const m of allMarkets) {
183
+ if (!m.venue)
184
+ m.venue = 'kalshi';
185
+ }
186
+ const combined = [...allMarkets, ...polyMarkets];
187
+ combined.sort((a, b) => b.liquidity - a.liquidity);
147
188
  if (json) {
148
- console.log(JSON.stringify(allMarkets, null, 2));
189
+ console.log(JSON.stringify(combined, null, 2));
149
190
  return;
150
191
  }
151
- (0, utils_js_1.header)(`${allMarkets.length} Live Markets`);
192
+ (0, utils_js_1.header)(`${combined.length} Live Markets (${allMarkets.length} Kalshi + ${polyMarkets.length} Polymarket)`);
152
193
  console.log(utils_js_1.c.bold +
153
- (0, utils_js_1.pad)('Ticker', 35) +
194
+ (0, utils_js_1.pad)('', 5) +
195
+ (0, utils_js_1.pad)('Ticker', 30) +
154
196
  (0, utils_js_1.rpad)('Yes', 6) +
155
197
  (0, utils_js_1.rpad)('Last', 6) +
156
198
  (0, utils_js_1.rpad)('Vol24h', 10) +
157
199
  (0, utils_js_1.rpad)('Liq', 10) +
158
200
  ' Title' +
159
201
  utils_js_1.c.reset);
160
- (0, utils_js_1.hr)(110);
161
- for (const m of allMarkets.slice(0, 50)) {
162
- console.log((0, utils_js_1.pad)(m.ticker, 35) +
202
+ (0, utils_js_1.hr)(120);
203
+ for (const m of combined.slice(0, 60)) {
204
+ // Venue tag: fixed 5-char column, color applied AFTER padding
205
+ const venuePad = m.venue === 'polymarket' ? 'POLY ' : 'KLSH ';
206
+ const venueColored = m.venue === 'polymarket'
207
+ ? utils_js_1.c.blue + venuePad + utils_js_1.c.reset
208
+ : utils_js_1.c.cyan + venuePad + utils_js_1.c.reset;
209
+ const tickerStr = m.venue === 'polymarket'
210
+ ? (m.ticker || '').slice(0, 28)
211
+ : (m.ticker || '').slice(0, 28);
212
+ console.log(venueColored +
213
+ (0, utils_js_1.pad)(tickerStr, 30) +
163
214
  (0, utils_js_1.rpad)(`${Math.round(m.yesAsk * 100)}¢`, 6) +
164
215
  (0, utils_js_1.rpad)(`${Math.round(m.lastPrice * 100)}¢`, 6) +
165
- (0, utils_js_1.rpad)((0, utils_js_1.vol)(m.volume24h), 10) +
166
- (0, utils_js_1.rpad)((0, utils_js_1.vol)(m.liquidity), 10) +
167
- ` ${m.title.slice(0, 55)}`);
216
+ (0, utils_js_1.rpad)((0, utils_js_1.vol)(Math.round(m.volume24h)), 10) +
217
+ (0, utils_js_1.rpad)((0, utils_js_1.vol)(Math.round(m.liquidity)), 10) +
218
+ ` ${(m.title || '').slice(0, 55)}`);
168
219
  }
169
220
  console.log('');
170
221
  }
@@ -18,6 +18,7 @@ interface SetupOpts {
18
18
  enableTrading?: boolean;
19
19
  disableTrading?: boolean;
20
20
  kalshi?: boolean;
21
+ polymarket?: boolean;
21
22
  }
22
23
  export declare function setupCommand(opts: SetupOpts): Promise<void>;
23
24
  export {};
@@ -165,6 +165,17 @@ async function setupCommand(opts) {
165
165
  blank();
166
166
  return;
167
167
  }
168
+ // ── sf setup --polymarket (reconfigure Polymarket credentials) ──────────
169
+ if (opts.polymarket) {
170
+ const existing = (0, config_js_1.loadFileConfig)();
171
+ blank();
172
+ console.log(` ${bold('Reconfigure Polymarket Credentials')}`);
173
+ blank();
174
+ await promptForPolymarket(existing);
175
+ (0, config_js_1.saveConfig)(existing);
176
+ blank();
177
+ return;
178
+ }
168
179
  // ── sf setup --enable-trading / --disable-trading ────────────────────────
169
180
  if (opts.enableTrading) {
170
181
  const existing = (0, config_js_1.loadFileConfig)();
@@ -211,6 +222,13 @@ async function showCheck() {
211
222
  else {
212
223
  info(`${dim('○')} KALSHI ${dim('skipped')}`);
213
224
  }
225
+ // Polymarket
226
+ if (config.polymarketWalletAddress) {
227
+ ok(`POLYMARKET ${dim(mask(config.polymarketWalletAddress))}`);
228
+ }
229
+ else {
230
+ info(`${dim('○')} POLYMARKET ${dim('skipped')}`);
231
+ }
214
232
  // Tavily
215
233
  if (config.tavilyKey) {
216
234
  ok(`TAVILY ${dim(mask(config.tavilyKey))}`);
@@ -318,9 +336,27 @@ async function runWizard() {
318
336
  }
319
337
  (0, config_js_1.saveConfig)(config);
320
338
  // ════════════════════════════════════════════════════════════════════════════
321
- // Step 4: Tavily
339
+ // Step 4: Polymarket
340
+ // ════════════════════════════════════════════════════════════════════════════
341
+ console.log(` ${bold('Step 4: Polymarket (optional)')}`);
342
+ blank();
343
+ const existingPolyWallet = process.env.POLYMARKET_WALLET_ADDRESS || config.polymarketWalletAddress;
344
+ if (existingPolyWallet) {
345
+ ok(`Detected wallet — ${dim(mask(existingPolyWallet))}`);
346
+ info(dim('Skipping.'));
347
+ config.polymarketWalletAddress = existingPolyWallet;
348
+ blank();
349
+ }
350
+ else {
351
+ await promptForPolymarket(config);
352
+ }
353
+ (0, config_js_1.saveConfig)(config);
354
+ if (config.polymarketWalletAddress)
355
+ process.env.POLYMARKET_WALLET_ADDRESS = config.polymarketWalletAddress;
356
+ // ════════════════════════════════════════════════════════════════════════════
357
+ // Step 5: Tavily
322
358
  // ════════════════════════════════════════════════════════════════════════════
323
- console.log(` ${bold('Step 4: News Search (optional)')}`);
359
+ console.log(` ${bold('Step 5: News Search (optional)')}`);
324
360
  blank();
325
361
  const existingTavily = process.env.TAVILY_API_KEY || config.tavilyKey;
326
362
  if (existingTavily) {
@@ -346,7 +382,7 @@ async function runWizard() {
346
382
  // Step 5: Trading
347
383
  // ════════════════════════════════════════════════════════════════════════════
348
384
  if (config.kalshiKeyId) {
349
- console.log(` ${bold('Step 5: Trading (optional)')}`);
385
+ console.log(` ${bold('Step 6: Trading (optional)')}`);
350
386
  blank();
351
387
  info('Warning: enabling this unlocks sf buy / sf sell / sf cancel.');
352
388
  info('Your Kalshi API key must have read+write permissions.');
@@ -493,7 +529,36 @@ async function promptForTavily() {
493
529
  blank();
494
530
  return answer;
495
531
  }
496
- // ─── Step 6: Thesis ──────────────────────────────────────────────────────────
532
+ async function promptForPolymarket(config) {
533
+ info('Connect Polymarket to view positions and scan orderbooks.');
534
+ info('Your Polygon wallet address is needed (starts with 0x...).');
535
+ info(`Find it at ${cyan('https://polymarket.com')} → Settings → Profile.`);
536
+ info('Press Enter to skip:');
537
+ blank();
538
+ const walletAddress = await prompt(' Wallet address > ');
539
+ if (!walletAddress) {
540
+ info(dim('Skipped.'));
541
+ blank();
542
+ return;
543
+ }
544
+ if (!walletAddress.startsWith('0x') || walletAddress.length < 40) {
545
+ fail('Invalid wallet address (must start with 0x and be 42 characters)');
546
+ info(dim('Saved anyway. You can fix it later with sf setup.'));
547
+ }
548
+ else {
549
+ ok(`Wallet: ${mask(walletAddress)}`);
550
+ }
551
+ config.polymarketWalletAddress = walletAddress;
552
+ // Optionally configure private key for future trading
553
+ info(dim('Private key (for future trading) — press Enter to skip:'));
554
+ const keyPath = await prompt(' Key path > ');
555
+ if (keyPath) {
556
+ config.polymarketPrivateKeyPath = keyPath;
557
+ ok(`Private key path: ${dim(keyPath)}`);
558
+ }
559
+ blank();
560
+ }
561
+ // ─── Step 7: Thesis ──────────────────────────────────────────────────────────
497
562
  async function handleThesisStep(config) {
498
563
  try {
499
564
  const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
@@ -501,7 +566,7 @@ async function handleThesisStep(config) {
501
566
  const theses = data.theses || [];
502
567
  const activeTheses = theses.filter((t) => t.status === 'active');
503
568
  if (activeTheses.length > 0) {
504
- console.log(` ${bold('Step 6: Theses')}`);
569
+ console.log(` ${bold('Step 7: Theses')}`);
505
570
  blank();
506
571
  ok(`Found ${activeTheses.length} active thesis(es):`);
507
572
  for (const t of activeTheses.slice(0, 5)) {
@@ -542,7 +607,7 @@ async function handleThesisStep(config) {
542
607
  return;
543
608
  }
544
609
  // No theses — offer to create one
545
- console.log(` ${bold('Step 6: Create Your First Thesis')}`);
610
+ console.log(` ${bold('Step 7: Create Your First Thesis')}`);
546
611
  blank();
547
612
  info('A thesis is your core market conviction. The system builds a causal model');
548
613
  info('from it, then continuously scans prediction markets for mispriced contracts.');
package/dist/config.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface SFConfig {
13
13
  openrouterKey?: string;
14
14
  kalshiKeyId?: string;
15
15
  kalshiPrivateKeyPath?: string;
16
+ polymarketWalletAddress?: string;
17
+ polymarketPrivateKeyPath?: string;
16
18
  tavilyKey?: string;
17
19
  model?: string;
18
20
  tradingEnabled?: boolean;
package/dist/config.js CHANGED
@@ -50,6 +50,8 @@ function loadConfig() {
50
50
  openrouterKey: process.env.OPENROUTER_API_KEY || file.openrouterKey,
51
51
  kalshiKeyId: process.env.KALSHI_API_KEY_ID || file.kalshiKeyId,
52
52
  kalshiPrivateKeyPath: process.env.KALSHI_PRIVATE_KEY_PATH || file.kalshiPrivateKeyPath,
53
+ polymarketWalletAddress: process.env.POLYMARKET_WALLET_ADDRESS || file.polymarketWalletAddress,
54
+ polymarketPrivateKeyPath: process.env.POLYMARKET_PRIVATE_KEY_PATH || file.polymarketPrivateKeyPath,
53
55
  tavilyKey: process.env.TAVILY_API_KEY || file.tavilyKey,
54
56
  model: process.env.SF_MODEL || file.model || DEFAULT_MODEL,
55
57
  tradingEnabled: file.tradingEnabled || false,
@@ -101,6 +103,12 @@ function applyConfig() {
101
103
  if (!process.env.KALSHI_PRIVATE_KEY_PATH && file.kalshiPrivateKeyPath) {
102
104
  process.env.KALSHI_PRIVATE_KEY_PATH = file.kalshiPrivateKeyPath;
103
105
  }
106
+ if (!process.env.POLYMARKET_WALLET_ADDRESS && file.polymarketWalletAddress) {
107
+ process.env.POLYMARKET_WALLET_ADDRESS = file.polymarketWalletAddress;
108
+ }
109
+ if (!process.env.POLYMARKET_PRIVATE_KEY_PATH && file.polymarketPrivateKeyPath) {
110
+ process.env.POLYMARKET_PRIVATE_KEY_PATH = file.polymarketPrivateKeyPath;
111
+ }
104
112
  if (!process.env.TAVILY_API_KEY && file.tavilyKey) {
105
113
  process.env.TAVILY_API_KEY = file.tavilyKey;
106
114
  }
package/dist/index.js CHANGED
@@ -52,18 +52,84 @@ const announcements_js_1 = require("./commands/announcements.js");
52
52
  const history_js_1 = require("./commands/history.js");
53
53
  const performance_js_1 = require("./commands/performance.js");
54
54
  const liquidity_js_1 = require("./commands/liquidity.js");
55
+ const book_js_1 = require("./commands/book.js");
55
56
  const telegram_js_1 = require("./commands/telegram.js");
56
57
  const utils_js_1 = require("./utils.js");
57
58
  // ── Apply ~/.sf/config.json to process.env BEFORE any command ────────────────
58
59
  // This means client.ts, kalshi.ts, agent.ts keep reading process.env and just work.
59
60
  (0, config_js_1.applyConfig)();
60
61
  const program = new commander_1.Command();
62
+ const GROUPED_HELP = `
63
+ \x1b[1mSimpleFunctions CLI\x1b[22m — prediction market thesis agent
64
+
65
+ \x1b[1mUsage:\x1b[22m sf <command> [options]
66
+ sf <command> --help for detailed options
67
+
68
+ \x1b[1mSetup\x1b[22m
69
+ \x1b[36msetup\x1b[39m Interactive config wizard
70
+ \x1b[36msetup --check\x1b[39m Show config status
71
+ \x1b[36msetup --polymarket\x1b[39m Configure Polymarket wallet
72
+
73
+ \x1b[1mThesis\x1b[22m
74
+ \x1b[36mlist\x1b[39m List all theses
75
+ \x1b[36mget\x1b[39m <id> Full thesis details
76
+ \x1b[36mcontext\x1b[39m <id> [--json] Thesis snapshot \x1b[2m(primary for agents)\x1b[22m
77
+ \x1b[36mcreate\x1b[39m "thesis" Create a new thesis
78
+ \x1b[36msignal\x1b[39m <id> "content" Inject a signal
79
+ \x1b[36mevaluate\x1b[39m <id> Trigger deep evaluation
80
+ \x1b[36mpublish\x1b[39m / \x1b[36munpublish\x1b[39m <id> Manage public visibility
81
+
82
+ \x1b[1mMarkets\x1b[22m
83
+ \x1b[36mscan\x1b[39m "keywords" Search Kalshi + Polymarket
84
+ \x1b[36mscan\x1b[39m --series TICKER Browse a Kalshi series
85
+ \x1b[36medges\x1b[39m [--json] Top edges across all theses
86
+ \x1b[36mwhatif\x1b[39m <id> What-if scenario analysis
87
+ \x1b[36mliquidity\x1b[39m [topic] Orderbook liquidity scanner
88
+ \x1b[36mbook\x1b[39m <ticker> [ticker2...] Orderbook depth for specific markets
89
+ \x1b[36mexplore\x1b[39m [slug] Browse public theses
90
+ \x1b[36mforecast\x1b[39m <event> Market distribution (P50/P75/P90)
91
+
92
+ \x1b[1mPortfolio\x1b[22m
93
+ \x1b[36mpositions\x1b[39m Kalshi + Polymarket positions
94
+ \x1b[36mbalance\x1b[39m Account balance
95
+ \x1b[36morders\x1b[39m Resting orders
96
+ \x1b[36mfills\x1b[39m Recent trade fills
97
+ \x1b[36msettlements\x1b[39m Settled contracts with P&L
98
+ \x1b[36mperformance\x1b[39m P&L over time
99
+ \x1b[36mdashboard\x1b[39m Interactive TUI overview
100
+
101
+ \x1b[1mTrading\x1b[22m \x1b[2m(requires sf setup --enable-trading)\x1b[22m
102
+ \x1b[36mbuy\x1b[39m <ticker> <qty> Buy contracts
103
+ \x1b[36msell\x1b[39m <ticker> <qty> Sell contracts
104
+ \x1b[36mcancel\x1b[39m [orderId] Cancel order(s)
105
+ \x1b[36mrfq\x1b[39m <ticker> <qty> Request for quote
106
+
107
+ \x1b[1mInteractive\x1b[22m
108
+ \x1b[36magent\x1b[39m [id] Agent with natural language + tools
109
+ \x1b[36mtelegram\x1b[39m Telegram bot for monitoring
110
+
111
+ \x1b[1mInfo\x1b[22m
112
+ \x1b[36mfeed\x1b[39m Evaluation history stream
113
+ \x1b[36mmilestones\x1b[39m Upcoming Kalshi events
114
+ \x1b[36mschedule\x1b[39m Exchange status
115
+ \x1b[36mannouncements\x1b[39m Exchange announcements
116
+ \x1b[36mhistory\x1b[39m <ticker> Historical market data
117
+ `;
61
118
  program
62
119
  .name('sf')
63
120
  .description('SimpleFunctions CLI — prediction market thesis agent')
64
121
  .version('0.1.0')
65
122
  .option('--api-key <key>', 'API key (or set SF_API_KEY env var)')
66
- .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)');
123
+ .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)')
124
+ .configureHelp({
125
+ formatHelp: (cmd, helper) => {
126
+ // For subcommands, use default help
127
+ if (cmd.parent)
128
+ return helper.formatHelp(cmd, helper);
129
+ // For main program, show grouped help
130
+ return GROUPED_HELP;
131
+ },
132
+ });
67
133
  // ── Pre-action guard: check configuration ────────────────────────────────────
68
134
  const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan', 'explore', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history', 'liquidity']);
69
135
  program.hook('preAction', (thisCommand, actionCommand) => {
@@ -91,16 +157,18 @@ program
91
157
  .option('--enable-trading', 'Enable trading (sf buy/sell/cancel)')
92
158
  .option('--disable-trading', 'Disable trading')
93
159
  .option('--kalshi', 'Reconfigure Kalshi API credentials')
160
+ .option('--polymarket', 'Reconfigure Polymarket wallet address')
94
161
  .action(async (opts) => {
95
- await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading, kalshi: opts.kalshi }));
162
+ await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading, kalshi: opts.kalshi, polymarket: opts.polymarket }));
96
163
  });
97
164
  // ── sf list ──────────────────────────────────────────────────────────────────
98
165
  program
99
166
  .command('list')
100
167
  .description('List all theses')
101
- .action(async (_opts, cmd) => {
168
+ .option('--json', 'JSON output')
169
+ .action(async (opts, cmd) => {
102
170
  const g = cmd.optsWithGlobals();
103
- await run(() => (0, list_js_1.listCommand)({ apiKey: g.apiKey, apiUrl: g.apiUrl }));
171
+ await run(() => (0, list_js_1.listCommand)({ json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
104
172
  });
105
173
  // ── sf get <id> ───────────────────────────────────────────────────────────────
106
174
  program
@@ -125,9 +193,10 @@ program
125
193
  .command('create <thesis>')
126
194
  .description('Create a new thesis (sync by default — waits for formation)')
127
195
  .option('--async', 'Async mode — return immediately without waiting')
196
+ .option('--json', 'JSON output')
128
197
  .action(async (thesis, opts, cmd) => {
129
198
  const g = cmd.optsWithGlobals();
130
- await run(() => (0, create_js_1.createCommand)(thesis, { async: opts.async, apiKey: g.apiKey, apiUrl: g.apiUrl }));
199
+ await run(() => (0, create_js_1.createCommand)(thesis, { async: opts.async, json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
131
200
  });
132
201
  // ── sf signal <id> <content> ──────────────────────────────────────────────────
133
202
  program
@@ -149,9 +218,10 @@ program
149
218
  // ── sf scan [query] ───────────────────────────────────────────────────────────
150
219
  program
151
220
  .command('scan [query]')
152
- .description('Explore Kalshi markets (no auth required)')
221
+ .description('Explore Kalshi + Polymarket markets')
153
222
  .option('--series <ticker>', 'List events + markets for a series (e.g. KXWTIMAX)')
154
223
  .option('--market <ticker>', 'Get single market detail (e.g. KXWTIMAX-26DEC31-T140)')
224
+ .option('--venue <venue>', 'Filter by venue: kalshi, polymarket, or all (default: all)')
155
225
  .option('--json', 'Output raw JSON')
156
226
  .action(async (query, opts, cmd) => {
157
227
  const g = cmd.optsWithGlobals();
@@ -163,6 +233,7 @@ program
163
233
  await run(() => (0, scan_js_1.scanCommand)(q, {
164
234
  series: opts.series,
165
235
  market: opts.market,
236
+ venue: opts.venue,
166
237
  json: opts.json,
167
238
  apiKey: g.apiKey,
168
239
  apiUrl: g.apiUrl,
@@ -402,14 +473,29 @@ program
402
473
  });
403
474
  // ── sf liquidity ─────────────────────────────────────────────────────────────
404
475
  program
405
- .command('liquidity')
406
- .description('Market liquidity scanner by topic and horizon')
407
- .option('--topic <topic>', 'Filter topic (oil, recession, fed, cpi, gas, sp500)')
476
+ .command('liquidity [topic]')
477
+ .description('Market liquidity scanner run without args to see topics')
478
+ .option('--all', 'Scan all topics')
479
+ .option('--venue <venue>', 'Filter venue: kalshi, polymarket, all (default: all)')
408
480
  .option('--horizon <horizon>', 'Filter horizon (weekly, monthly, long-term)')
409
481
  .option('--min-depth <depth>', 'Minimum bid+ask depth', parseInt)
410
482
  .option('--json', 'JSON output')
411
- .action(async (opts) => {
412
- await run(() => (0, liquidity_js_1.liquidityCommand)(opts));
483
+ .action(async (topic, opts) => {
484
+ await run(() => (0, liquidity_js_1.liquidityCommand)({ ...opts, topic }));
485
+ });
486
+ // ── sf book ──────────────────────────────────────────────────────────────────
487
+ program
488
+ .command('book [tickers...]')
489
+ .description('Orderbook depth, spread, and liquidity for specific markets')
490
+ .option('--poly <query>', 'Search Polymarket markets by keyword')
491
+ .option('--history', 'Include 7-day price history sparkline')
492
+ .option('--json', 'JSON output')
493
+ .action(async (tickers, opts) => {
494
+ if (tickers.length === 0 && !opts.poly) {
495
+ console.error('Usage: sf book <ticker> [ticker2...] OR sf book --poly "oil price"');
496
+ process.exit(1);
497
+ }
498
+ await run(() => (0, book_js_1.bookCommand)(tickers, opts));
413
499
  });
414
500
  // ── sf telegram ──────────────────────────────────────────────────────────────
415
501
  program