@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.
- package/dist/commands/agent.js +305 -72
- package/dist/commands/balance.js +1 -1
- package/dist/commands/book.js +1 -1
- package/dist/commands/context.d.ts +1 -0
- package/dist/commands/context.js +5 -0
- package/dist/commands/edges.d.ts +1 -0
- package/dist/commands/edges.js +28 -13
- package/dist/commands/explore.d.ts +1 -0
- package/dist/commands/explore.js +7 -1
- package/dist/commands/fills.js +1 -1
- package/dist/commands/list.js +1 -1
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +4 -4
- package/dist/commands/markets.d.ts +1 -0
- package/dist/commands/markets.js +5 -0
- package/dist/commands/positions.js +2 -2
- package/dist/commands/query.d.ts +1 -0
- package/dist/commands/query.js +6 -1
- package/dist/commands/scan.d.ts +1 -0
- package/dist/commands/scan.js +8 -3
- package/dist/commands/watch.d.ts +19 -0
- package/dist/commands/watch.js +157 -0
- package/dist/config.js +2 -2
- package/dist/index.js +54 -10
- package/dist/share.d.ts +4 -0
- package/dist/share.js +27 -0
- package/dist/skills/loader.d.ts +19 -0
- package/dist/skills/loader.js +86 -0
- package/dist/types/output.d.ts +412 -0
- package/dist/types/output.js +9 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +22 -0
- package/package.json +2 -1
package/dist/commands/edges.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ──────────────────────────────────────────────────
|
package/dist/commands/explore.js
CHANGED
|
@@ -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(
|
|
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) {
|
package/dist/commands/fills.js
CHANGED
|
@@ -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.
|
|
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;
|
package/dist/commands/list.js
CHANGED
|
@@ -11,7 +11,7 @@ async function listCommand(opts) {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
if (theses.length === 0) {
|
|
14
|
-
|
|
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}` +
|
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
20
|
-
console.log(` ${utils_js_1.c.dim}Run ${utils_js_1.c.cyan}sf
|
|
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
|
package/dist/commands/markets.js
CHANGED
|
@@ -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
|
|
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.
|
|
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) {
|
package/dist/commands/query.d.ts
CHANGED
package/dist/commands/query.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.queryCommand = queryCommand;
|
|
14
|
-
const
|
|
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;
|
package/dist/commands/scan.d.ts
CHANGED
package/dist/commands/scan.js
CHANGED
|
@@ -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} "
|
|
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
|
|
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
|
|
133
|
+
console.error('\n Kalshi API key not configured. Run: sf setup --kalshi\n');
|
|
134
134
|
process.exit(1);
|
|
135
135
|
}
|
|
136
136
|
}
|