@spfunctions/cli 1.7.16 → 1.7.19

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.
@@ -17,6 +17,7 @@ exports.edgesCommand = edgesCommand;
17
17
  const client_js_1 = require("../client.js");
18
18
  const kalshi_js_1 = require("../kalshi.js");
19
19
  const utils_js_1 = require("../utils.js");
20
+ const share_js_1 = require("../share.js");
20
21
  async function edgesCommand(opts) {
21
22
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
22
23
  const limit = parseInt(opts.limit || '20');
@@ -27,23 +28,28 @@ async function edgesCommand(opts) {
27
28
  const liqRank = { a: 4, high: 4, b: 3, medium: 3, c: 2, low: 2, d: 1 };
28
29
  // ── Step 1: Fetch theses (or single thesis) ────────────────────────────────
29
30
  let theses;
31
+ const log = opts.json ? (() => { }) : (msg) => console.log(msg);
30
32
  if (opts.thesis) {
31
33
  // Single thesis mode — skip listing, fetch context directly
32
- console.log(`${utils_js_1.c.dim}Fetching edges for thesis ${opts.thesis}...${utils_js_1.c.reset}`);
34
+ log(`${utils_js_1.c.dim}Fetching edges for thesis ${opts.thesis}...${utils_js_1.c.reset}`);
33
35
  theses = [{ id: opts.thesis }];
34
36
  }
35
37
  else {
36
- console.log(`${utils_js_1.c.dim}Fetching theses...${utils_js_1.c.reset}`);
38
+ log(`${utils_js_1.c.dim}Fetching theses...${utils_js_1.c.reset}`);
37
39
  const data = await client.listTheses();
38
40
  const rawTheses = data.theses || data;
39
41
  theses = (Array.isArray(rawTheses) ? rawTheses : []).filter((t) => t.status === 'active');
40
42
  }
41
43
  if (theses.length === 0) {
42
- console.log(`${utils_js_1.c.yellow}No active theses found.${utils_js_1.c.reset} Create one: sf create "your thesis"`);
44
+ if (opts.json) {
45
+ console.log(JSON.stringify({ edges: [], totalEdges: 0 }));
46
+ return;
47
+ }
48
+ (0, utils_js_1.emptyState)('active theses', 'Create one: sf create "your market thesis"');
43
49
  return;
44
50
  }
45
51
  // ── Step 2: Fetch context for each thesis (parallel) ───────────────────────
46
- console.log(`${utils_js_1.c.dim}Fetching edges from ${theses.length} ${theses.length === 1 ? 'thesis' : 'theses'}...${utils_js_1.c.reset}`);
52
+ log(`${utils_js_1.c.dim}Fetching edges from ${theses.length} ${theses.length === 1 ? 'thesis' : 'theses'}...${utils_js_1.c.reset}`);
47
53
  const allEdges = [];
48
54
  const contextPromises = theses.map(async (t) => {
49
55
  try {
@@ -74,7 +80,11 @@ async function edgesCommand(opts) {
74
80
  }
75
81
  }
76
82
  if (allEdges.length === 0) {
77
- console.log(`${utils_js_1.c.yellow}No edges found across ${theses.length} theses.${utils_js_1.c.reset}`);
83
+ if (opts.json) {
84
+ console.log(JSON.stringify({ edges: [], totalEdges: 0, thesesScanned: theses.length }));
85
+ return;
86
+ }
87
+ (0, utils_js_1.emptyState)('edges', `Scanned ${theses.length} theses. Edges appear when thesis price diverges from market price.`);
78
88
  return;
79
89
  }
80
90
  // ── Step 3: Dedupe by marketId — keep highest absolute edge ────────────────
@@ -99,7 +109,7 @@ async function edgesCommand(opts) {
99
109
  // ── Step 4: Fetch positions (optional) ─────────────────────────────────────
100
110
  let positions = null;
101
111
  if ((0, kalshi_js_1.isKalshiConfigured)()) {
102
- console.log(`${utils_js_1.c.dim}Fetching Kalshi positions...${utils_js_1.c.reset}`);
112
+ log(`${utils_js_1.c.dim}Fetching Kalshi positions...${utils_js_1.c.reset}`);
103
113
  positions = await (0, kalshi_js_1.getPositions)();
104
114
  if (positions) {
105
115
  // Enrich with live prices
@@ -141,14 +151,19 @@ async function edgesCommand(opts) {
141
151
  }
142
152
  // Apply limit
143
153
  const display = merged.slice(0, limit);
144
- // ── Step 6: JSON output ────────────────────────────────────────────────────
154
+ // ── Step 6: JSON / share output ────────────────────────────────────────────
155
+ const outputData = {
156
+ totalEdges: merged.length,
157
+ displayed: display.length,
158
+ thesesScanned: theses.length,
159
+ edges: display,
160
+ };
161
+ if (opts.share) {
162
+ await (0, share_js_1.shareOutput)('edges', '', outputData);
163
+ return;
164
+ }
145
165
  if (opts.json) {
146
- console.log(JSON.stringify({
147
- totalEdges: merged.length,
148
- displayed: display.length,
149
- thesesScanned: theses.length,
150
- edges: display,
151
- }, null, 2));
166
+ console.log(JSON.stringify(outputData, null, 2));
152
167
  return;
153
168
  }
154
169
  // ── Step 6: Pretty output ──────────────────────────────────────────────────
@@ -10,4 +10,5 @@
10
10
  */
11
11
  export declare function exploreCommand(slug?: string, opts?: {
12
12
  json?: boolean;
13
+ share?: boolean;
13
14
  }): Promise<void>;
@@ -11,6 +11,8 @@
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.exploreCommand = exploreCommand;
14
+ const share_js_1 = require("../share.js");
15
+ const utils_js_1 = require("../utils.js");
14
16
  const BASE_URL = 'https://simplefunctions.dev';
15
17
  async function exploreCommand(slug, opts) {
16
18
  if (!slug) {
@@ -21,13 +23,17 @@ async function exploreCommand(slug, opts) {
21
23
  return;
22
24
  }
23
25
  const { theses } = await res.json();
26
+ if (opts?.share) {
27
+ await (0, share_js_1.shareOutput)('explore', '', { theses });
28
+ return;
29
+ }
24
30
  if (opts?.json) {
25
31
  console.log(JSON.stringify(theses, null, 2));
26
32
  return;
27
33
  }
28
34
  console.log('\n Public Theses\n');
29
35
  if (theses.length === 0) {
30
- console.log(' No public theses yet.\n');
36
+ console.log(` ${utils_js_1.c.dim}No public theses yet. Publish yours: sf publish <id>${utils_js_1.c.reset}\n`);
31
37
  return;
32
38
  }
33
39
  for (const t of theses) {
@@ -6,7 +6,7 @@ const utils_js_1 = require("../utils.js");
6
6
  async function fillsCommand(opts) {
7
7
  const result = await (0, kalshi_js_1.getFills)({ ticker: opts.ticker, limit: 50 });
8
8
  if (!result)
9
- throw new Error('Kalshi not configured. Set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY_PATH.');
9
+ throw new Error('Kalshi not configured. Run: sf setup --kalshi');
10
10
  if (opts.json) {
11
11
  console.log(JSON.stringify(result.fills, null, 2));
12
12
  return;
@@ -11,7 +11,7 @@ async function listCommand(opts) {
11
11
  return;
12
12
  }
13
13
  if (theses.length === 0) {
14
- console.log(`${utils_js_1.c.dim}No theses found.${utils_js_1.c.reset}`);
14
+ (0, utils_js_1.emptyState)('theses', 'Create one: sf create "your market thesis"');
15
15
  return;
16
16
  }
17
17
  console.log(`\n${utils_js_1.c.bold}` +
@@ -6,4 +6,5 @@
6
6
  */
7
7
  export declare function loginCommand(opts: {
8
8
  apiUrl?: string;
9
+ force?: boolean;
9
10
  }): Promise<void>;
@@ -13,11 +13,11 @@ const utils_js_1 = require("../utils.js");
13
13
  const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
14
14
  async function loginCommand(opts) {
15
15
  const apiUrl = opts.apiUrl || (0, config_js_1.loadConfig)().apiUrl || SF_API_URL;
16
- // Only block if the CONFIG FILE has an API key (not env vars)
16
+ // If already configured, re-login replaces the existing key
17
17
  const fileConfig = (0, config_js_1.loadFileConfig)();
18
- if (fileConfig.apiKey) {
19
- console.log(`\n ${utils_js_1.c.dim}Already configured with API key ${fileConfig.apiKey.slice(0, 12)}...${utils_js_1.c.reset}`);
20
- console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf setup --reset${utils_js_1.c.dim} first to reconfigure.${utils_js_1.c.reset}\n`);
18
+ if (fileConfig.apiKey && !opts.force) {
19
+ console.log(`\n ${utils_js_1.c.dim}Already logged in (${fileConfig.apiKey.slice(0, 12)}...).${utils_js_1.c.reset}`);
20
+ console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf login --force${utils_js_1.c.dim} to re-authenticate, or ${utils_js_1.c.cyan}sf logout${utils_js_1.c.dim} to clear.${utils_js_1.c.reset}\n`);
21
21
  return;
22
22
  }
23
23
  // Generate session token
@@ -6,4 +6,5 @@
6
6
  */
7
7
  export declare function marketsCommand(opts?: {
8
8
  json?: boolean;
9
+ share?: boolean;
9
10
  }): Promise<void>;
@@ -7,6 +7,7 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.marketsCommand = marketsCommand;
10
+ const share_js_1 = require("../share.js");
10
11
  const BASE_URL = 'https://simplefunctions.dev';
11
12
  async function marketsCommand(opts) {
12
13
  const res = await fetch(`${BASE_URL}/api/public/markets`);
@@ -15,6 +16,10 @@ async function marketsCommand(opts) {
15
16
  return;
16
17
  }
17
18
  const data = await res.json();
19
+ if (opts?.share) {
20
+ await (0, share_js_1.shareOutput)('markets', '', data);
21
+ return;
22
+ }
18
23
  if (opts?.json) {
19
24
  console.log(JSON.stringify(data, null, 2));
20
25
  return;
@@ -180,10 +180,10 @@ async function positionsCommand(opts) {
180
180
  console.log('');
181
181
  }
182
182
  else if ((0, kalshi_js_1.isKalshiConfigured)()) {
183
- console.log(`\n${utils_js_1.c.dim}No open positions on Kalshi.${utils_js_1.c.reset}\n`);
183
+ console.log(`\n ${utils_js_1.c.dim}No open Kalshi positions.${utils_js_1.c.reset}\n`);
184
184
  }
185
185
  else {
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`);
186
+ console.log(`\n ${utils_js_1.c.dim}Kalshi not configured. Run: ${utils_js_1.c.cyan}sf setup --kalshi${utils_js_1.c.reset}\n`);
187
187
  }
188
188
  // C) Polymarket positions
189
189
  if (polyPositions.length > 0) {
@@ -10,5 +10,6 @@
10
10
  */
11
11
  export declare function queryCommand(q: string, opts?: {
12
12
  json?: boolean;
13
+ share?: boolean;
13
14
  limit?: string;
14
15
  }): Promise<void>;
@@ -11,7 +11,8 @@
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.queryCommand = queryCommand;
14
- const BASE_URL = 'https://simplefunctions.dev';
14
+ const share_js_1 = require("../share.js");
15
+ const BASE_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
15
16
  async function queryCommand(q, opts) {
16
17
  const limit = opts?.limit || '10';
17
18
  const url = `${BASE_URL}/api/public/query?q=${encodeURIComponent(q)}&limit=${limit}`;
@@ -27,6 +28,10 @@ async function queryCommand(q, opts) {
27
28
  return;
28
29
  }
29
30
  const data = await res.json();
31
+ if (opts?.share) {
32
+ await (0, share_js_1.shareOutput)('query', q, data);
33
+ return;
34
+ }
30
35
  if (opts?.json) {
31
36
  console.log(JSON.stringify(data, null, 2));
32
37
  return;
@@ -3,6 +3,7 @@ interface ScanOpts {
3
3
  market?: string;
4
4
  venue?: string;
5
5
  json?: boolean;
6
+ share?: boolean;
6
7
  apiKey?: string;
7
8
  apiUrl?: string;
8
9
  }
@@ -4,6 +4,7 @@ exports.scanCommand = scanCommand;
4
4
  const client_js_1 = require("../client.js");
5
5
  const polymarket_js_1 = require("../polymarket.js");
6
6
  const utils_js_1 = require("../utils.js");
7
+ const share_js_1 = require("../share.js");
7
8
  // SF API base — scan goes through the server which holds the proxy key
8
9
  const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
9
10
  async function scanCommand(query, opts) {
@@ -18,7 +19,7 @@ async function scanCommand(query, opts) {
18
19
  return;
19
20
  }
20
21
  // Mode 3: keyword scan across all series + Polymarket
21
- await keywordScan(query, opts.json, opts.venue || 'all');
22
+ await keywordScan(query, opts.json, opts.venue || 'all', opts.share);
22
23
  }
23
24
  async function showMarket(ticker, json) {
24
25
  console.log(`${utils_js_1.c.dim}Fetching market ${ticker}...${utils_js_1.c.reset}`);
@@ -80,7 +81,7 @@ async function showSeries(seriesTicker, json) {
80
81
  printMarketsTable(markets);
81
82
  }
82
83
  }
83
- async function keywordScan(query, json, venue = 'all') {
84
+ async function keywordScan(query, json, venue = 'all', share) {
84
85
  // ── Polymarket search (runs in parallel with Kalshi) ───────────────────────
85
86
  const polyPromise = (venue === 'kalshi')
86
87
  ? Promise.resolve([])
@@ -154,6 +155,10 @@ async function keywordScan(query, json, venue = 'all') {
154
155
  }
155
156
  const combined = [...allMarkets, ...polyMarkets];
156
157
  combined.sort((a, b) => b.liquidity - a.liquidity);
158
+ if (share) {
159
+ await (0, share_js_1.shareOutput)('scan', query, combined);
160
+ return;
161
+ }
157
162
  if (json) {
158
163
  console.log(JSON.stringify(combined, null, 2));
159
164
  return;
@@ -189,7 +194,7 @@ async function keywordScan(query, json, venue = 'all') {
189
194
  console.log('');
190
195
  // Conversion hook
191
196
  if (!json) {
192
- console.log(`${utils_js_1.c.dim}Want 24/7 monitoring + edge detection? ${utils_js_1.c.reset}${utils_js_1.c.cyan}sf create${utils_js_1.c.reset}${utils_js_1.c.dim} "your thesis"${utils_js_1.c.reset}`);
197
+ console.log(`${utils_js_1.c.dim}Want 24/7 monitoring + edge detection? ${utils_js_1.c.reset}${utils_js_1.c.cyan}sf create${utils_js_1.c.reset}${utils_js_1.c.dim} "${query.slice(0, 50)}"${utils_js_1.c.reset}`);
193
198
  console.log('');
194
199
  }
195
200
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * sf watch
3
+ *
4
+ * Push-mode monitoring. Polls GET /api/changes?since= for server-side
5
+ * detected market changes. No client-side diffing — the server handles
6
+ * change detection every 15 minutes via the scan-prices cron.
7
+ *
8
+ * Usage:
9
+ * sf watch "gold" — Watch gold-related changes
10
+ * sf watch "iran oil" — Watch iran/oil changes
11
+ * sf watch — Watch all market changes
12
+ * sf watch --json — JSON change events for piping
13
+ */
14
+ interface WatchOpts {
15
+ interval?: string;
16
+ json?: boolean;
17
+ }
18
+ export declare function watchCommand(query: string | undefined, opts: WatchOpts): Promise<void>;
19
+ export {};
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * sf watch
4
+ *
5
+ * Push-mode monitoring. Polls GET /api/changes?since= for server-side
6
+ * detected market changes. No client-side diffing — the server handles
7
+ * change detection every 15 minutes via the scan-prices cron.
8
+ *
9
+ * Usage:
10
+ * sf watch "gold" — Watch gold-related changes
11
+ * sf watch "iran oil" — Watch iran/oil changes
12
+ * sf watch — Watch all market changes
13
+ * sf watch --json — JSON change events for piping
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.watchCommand = watchCommand;
17
+ const utils_js_1 = require("../utils.js");
18
+ const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
19
+ async function watchCommand(query, opts) {
20
+ const intervalSec = parseInt(opts.interval || '60');
21
+ const intervalMs = intervalSec * 1000;
22
+ const isJson = opts.json || false;
23
+ if (!isJson) {
24
+ console.log();
25
+ console.log(` ${utils_js_1.c.bold}sf watch${utils_js_1.c.reset}${query ? ` "${query}"` : ''}`);
26
+ console.log(` ${utils_js_1.c.dim}Polling /api/changes every ${intervalSec}s · Ctrl+C to stop${utils_js_1.c.reset}`);
27
+ console.log();
28
+ }
29
+ let lastSeen = new Date().toISOString();
30
+ // Handle Ctrl+C gracefully
31
+ process.on('SIGINT', () => {
32
+ if (!isJson) {
33
+ console.log(`\n ${utils_js_1.c.dim}Watch stopped.${utils_js_1.c.reset}\n`);
34
+ }
35
+ process.exit(0);
36
+ });
37
+ // First poll: show recent changes from last hour
38
+ try {
39
+ const initialSince = new Date(Date.now() - 60 * 60 * 1000).toISOString();
40
+ const changes = await fetchChanges(initialSince, query);
41
+ if (changes.length > 0) {
42
+ if (!isJson) {
43
+ console.log(` ${utils_js_1.c.dim}${ts()} Recent changes (last hour):${utils_js_1.c.reset}`);
44
+ }
45
+ const deduped = dedupeByTitle(changes);
46
+ for (const change of deduped) {
47
+ if (isJson) {
48
+ console.log(JSON.stringify(change));
49
+ }
50
+ else {
51
+ printChange(change);
52
+ }
53
+ }
54
+ lastSeen = changes[0].detectedAt;
55
+ }
56
+ else if (!isJson) {
57
+ console.log(` ${utils_js_1.c.dim}${ts()} No recent changes. Watching...${utils_js_1.c.reset}`);
58
+ }
59
+ }
60
+ catch (err) {
61
+ if (!isJson) {
62
+ console.error(` ${utils_js_1.c.dim}${ts()} Error: ${err.message}${utils_js_1.c.reset}`);
63
+ }
64
+ }
65
+ // Polling loop
66
+ while (true) {
67
+ await sleep(intervalMs);
68
+ try {
69
+ const changes = await fetchChanges(lastSeen, query);
70
+ const deduped = isJson ? changes : dedupeByTitle(changes);
71
+ for (const change of deduped) {
72
+ if (isJson) {
73
+ console.log(JSON.stringify(change));
74
+ }
75
+ else {
76
+ printChange(change);
77
+ }
78
+ }
79
+ if (changes.length > 0) {
80
+ lastSeen = changes[0].detectedAt;
81
+ }
82
+ else if (!isJson) {
83
+ process.stdout.write(` ${utils_js_1.c.dim}${ts()} · no new changes${utils_js_1.c.reset}\r`);
84
+ }
85
+ }
86
+ catch (err) {
87
+ if (!isJson) {
88
+ console.error(` ${utils_js_1.c.dim}${ts()} Error: ${err.message}${utils_js_1.c.reset}`);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ // Group same-title same-type changes into one line (e.g. 12 FaZe CEO contracts → 1 line)
94
+ function dedupeByTitle(changes) {
95
+ const groups = new Map();
96
+ for (const c of changes) {
97
+ const key = `${c.changeType}:${c.title}`;
98
+ const existing = groups.get(key);
99
+ if (existing) {
100
+ existing.count++;
101
+ }
102
+ else {
103
+ groups.set(key, { count: 1, representative: c });
104
+ }
105
+ }
106
+ return Array.from(groups.values()).map(({ count, representative }) => {
107
+ if (count > 1) {
108
+ return { ...representative, title: `${representative.title} (${count} contracts)` };
109
+ }
110
+ return representative;
111
+ });
112
+ }
113
+ async function fetchChanges(since, query) {
114
+ let url = `${SF_API_URL}/api/changes?since=${encodeURIComponent(since)}&limit=50`;
115
+ if (query)
116
+ url += `&q=${encodeURIComponent(query)}`;
117
+ const res = await fetch(url);
118
+ if (!res.ok)
119
+ return [];
120
+ const data = await res.json();
121
+ return data.changes || [];
122
+ }
123
+ function printChange(change) {
124
+ const time = ts();
125
+ const venue = change.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` :
126
+ change.venue === 'polymarket' ? `\x1b[35mP\x1b[39m` :
127
+ change.venue === 'traditional' ? `${utils_js_1.c.dim}$${utils_js_1.c.reset}` : '·';
128
+ switch (change.changeType) {
129
+ case 'new_contract':
130
+ console.log(` ${time} ${venue} ${utils_js_1.c.green}+ NEW${utils_js_1.c.reset} ${change.afterPrice}¢ ${change.title}`);
131
+ break;
132
+ case 'removed_contract':
133
+ console.log(` ${time} ${venue} ${utils_js_1.c.red}− GONE${utils_js_1.c.reset} ${change.title}`);
134
+ break;
135
+ case 'price_move': {
136
+ if (change.venue === 'traditional') {
137
+ const sign = (change.delta || 0) > 0 ? '+' : '';
138
+ const color = (change.delta || 0) > 0 ? utils_js_1.c.green : utils_js_1.c.red;
139
+ const beforeDollars = ((change.beforePrice || 0) / 100).toFixed(0);
140
+ const afterDollars = ((change.afterPrice || 0) / 100).toFixed(0);
141
+ console.log(` ${time} ${venue} ${color}${sign}${change.delta}¢${utils_js_1.c.reset} $${beforeDollars}→$${afterDollars} ${change.title}`);
142
+ }
143
+ else {
144
+ const sign = (change.delta || 0) > 0 ? '+' : '';
145
+ const color = (change.delta || 0) > 0 ? utils_js_1.c.green : utils_js_1.c.red;
146
+ console.log(` ${time} ${venue} ${color}${sign}${change.delta}¢${utils_js_1.c.reset} ${change.beforePrice}→${change.afterPrice}¢ ${change.title}`);
147
+ }
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ function ts() {
153
+ return new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
154
+ }
155
+ function sleep(ms) {
156
+ return new Promise(resolve => setTimeout(resolve, ms));
157
+ }
package/dist/config.js CHANGED
@@ -126,11 +126,11 @@ function isConfigured() {
126
126
  function requireTrading() {
127
127
  const config = loadConfig();
128
128
  if (!config.tradingEnabled) {
129
- console.error('\n ⚠️ Trading is disabled. Run: sf setup --enable-trading\n');
129
+ console.error('\n Trading is disabled. Run: sf setup --enable-trading\n');
130
130
  process.exit(1);
131
131
  }
132
132
  if (!config.kalshiKeyId && !process.env.KALSHI_API_KEY_ID) {
133
- console.error('\n ⚠️ Kalshi API key not configured. Run: sf setup\n');
133
+ console.error('\n Kalshi API key not configured. Run: sf setup --kalshi\n');
134
134
  process.exit(1);
135
135
  }
136
136
  }