@spfunctions/cli 1.7.10 → 1.7.11

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.
@@ -4,10 +4,11 @@ exports.contextCommand = contextCommand;
4
4
  const client_js_1 = require("../client.js");
5
5
  const kalshi_js_1 = require("../kalshi.js");
6
6
  const utils_js_1 = require("../utils.js");
7
+ const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
7
8
  async function contextCommand(id, opts) {
8
- // No thesis ID → global market snapshot
9
+ // ── Mode 1: No thesis ID → global market intelligence ─────────────────────
9
10
  if (!id) {
10
- const res = await fetch('https://simplefunctions.dev/api/public/context');
11
+ const res = await fetch(`${opts.apiUrl || SF_API_URL}/api/public/context`);
11
12
  if (!res.ok) {
12
13
  console.error(` Error: ${res.status} ${await res.text()}`);
13
14
  return;
@@ -17,63 +18,103 @@ async function contextCommand(id, opts) {
17
18
  console.log(JSON.stringify(data, null, 2));
18
19
  return;
19
20
  }
20
- console.log(`\n${utils_js_1.c.bold}Market Snapshot${utils_js_1.c.reset} ${(0, utils_js_1.shortDate)(data.snapshotAt)}\n`);
21
- if (data.edges?.length > 0) {
22
- console.log(`${utils_js_1.c.bold}Thesis Edges${utils_js_1.c.reset}`);
23
- for (const m of data.edges.slice(0, 10)) {
21
+ const scanTime = data.scannedAt ? (0, utils_js_1.shortDate)(data.scannedAt) : 'no scan yet';
22
+ console.log(`\n${utils_js_1.c.bold}Markets${utils_js_1.c.reset} ${utils_js_1.c.dim}scan: ${scanTime}${utils_js_1.c.reset}\n`);
23
+ // ── Traditional markets ticker bar ──────────────────────────────────────
24
+ const trad = data.traditional || [];
25
+ if (trad.length > 0) {
26
+ const line = trad.map((m) => {
27
+ const ch = m.changePct || 0;
28
+ const color = ch > 0 ? utils_js_1.c.green : ch < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
29
+ const sign = ch > 0 ? '+' : '';
30
+ return `${utils_js_1.c.bold}${m.symbol}${utils_js_1.c.reset} ${m.price} ${color}${sign}${ch.toFixed(1)}%${utils_js_1.c.reset}`;
31
+ }).join(' ');
32
+ console.log(` ${line}`);
33
+ console.log();
34
+ }
35
+ // ── Movers ─────────────────────────────────────────────────────────────
36
+ const movers = data.movers || [];
37
+ if (movers.length > 0) {
38
+ console.log(`${utils_js_1.c.bold}Movers (24h)${utils_js_1.c.reset}`);
39
+ for (const m of movers.slice(0, 8)) {
24
40
  const venue = m.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
25
- const edgeStr = m.edge > 0 ? `${utils_js_1.c.green}+${m.edge}¢${utils_js_1.c.reset}` : `${utils_js_1.c.red}${m.edge}¢${utils_js_1.c.reset}`;
26
- console.log(` ${venue} ${String(m.price).padStart(3)}¢ edge ${edgeStr.padStart(14)} ${m.title.slice(0, 50)}`);
41
+ const ch = m.change24h || 0;
42
+ const chColor = ch > 0 ? utils_js_1.c.green : utils_js_1.c.red;
43
+ const chStr = `${chColor}${ch > 0 ? '+' : ''}${ch}¢${utils_js_1.c.reset}`;
44
+ const cat = m.category ? `${utils_js_1.c.dim}[${m.category}]${utils_js_1.c.reset}` : '';
45
+ console.log(` ${venue} ${(0, utils_js_1.rpad)(`${m.price}¢`, 5)} ${(0, utils_js_1.rpad)(chStr, 16)} ${(0, utils_js_1.trunc)(m.title, 50)} ${cat}`);
46
+ // Actionable: show command to dig deeper
47
+ if (m.venue === 'kalshi' && m.ticker) {
48
+ console.log(` ${utils_js_1.c.dim}→ sf book ${m.ticker}${utils_js_1.c.reset}`);
49
+ }
50
+ else {
51
+ console.log(` ${utils_js_1.c.dim}→ sf query "${extractQuery(m.title)}"${utils_js_1.c.reset}`);
52
+ }
27
53
  }
28
54
  console.log();
29
55
  }
30
- if (data.movers?.length > 0) {
31
- console.log(`${utils_js_1.c.bold}Movers (24h)${utils_js_1.c.reset}`);
32
- for (const m of data.movers.slice(0, 8)) {
56
+ // ── Liquid markets ─────────────────────────────────────────────────────
57
+ const liquid = data.liquid || [];
58
+ if (liquid.length > 0) {
59
+ console.log(`${utils_js_1.c.bold}Most Liquid${utils_js_1.c.reset}`);
60
+ for (const m of liquid.slice(0, 6)) {
33
61
  const venue = m.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
34
- const ch = m.change24h > 0 ? `${utils_js_1.c.green}+${m.change24h}¢${utils_js_1.c.reset}` : `${utils_js_1.c.red}${m.change24h}¢${utils_js_1.c.reset}`;
35
- console.log(` ${venue} ${String(m.price).padStart(3)}¢ ${ch.padStart(16)} ${m.title.slice(0, 55)}`);
62
+ console.log(` ${venue} ${(0, utils_js_1.rpad)(`${m.price}¢`, 5)} spread ${m.spread}¢ vol ${(0, utils_js_1.vol)(Math.round(m.volume24h))} ${(0, utils_js_1.trunc)(m.title, 45)}`);
36
63
  }
37
64
  console.log();
38
65
  }
39
- if (data.milestones?.length > 0) {
40
- console.log(`${utils_js_1.c.bold}Upcoming (48h)${utils_js_1.c.reset}`);
41
- for (const m of data.milestones.slice(0, 8)) {
42
- console.log(` ${utils_js_1.c.dim}${String(m.hoursUntil).padStart(3)}h${utils_js_1.c.reset} ${m.title.slice(0, 55)} ${utils_js_1.c.dim}${m.category}${utils_js_1.c.reset}`);
66
+ // ── Categories ─────────────────────────────────────────────────────────
67
+ const cats = data.categories || [];
68
+ if (cats.length > 0) {
69
+ console.log(`${utils_js_1.c.bold}Categories${utils_js_1.c.reset}`);
70
+ for (const cat of cats.slice(0, 8)) {
71
+ const mover = cat.topMover;
72
+ const moverStr = mover
73
+ ? `${utils_js_1.c.dim}top: ${(0, utils_js_1.trunc)(mover.title, 30)} ${mover.change24h > 0 ? utils_js_1.c.green + '+' : utils_js_1.c.red}${mover.change24h}¢${utils_js_1.c.reset}`
74
+ : '';
75
+ console.log(` ${(0, utils_js_1.pad)(cat.name, 14)} ${(0, utils_js_1.rpad)(String(cat.marketCount) + ' mkts', 10)} vol ${(0, utils_js_1.vol)(cat.totalVolume24h)} ${moverStr}`);
43
76
  }
44
77
  console.log();
45
78
  }
46
- if (data.liquid?.length > 0) {
47
- console.log(`${utils_js_1.c.bold}Liquid${utils_js_1.c.reset}`);
48
- for (const m of data.liquid.slice(0, 8)) {
49
- const venue = m.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
50
- console.log(` ${venue} ${String(m.price).padStart(3)}¢ spread ${m.spread}¢ ${m.title.slice(0, 55)}`);
79
+ // ── Thesis edges (from public theses) ──────────────────────────────────
80
+ const edges = data.edges || [];
81
+ if (edges.length > 0) {
82
+ console.log(`${utils_js_1.c.bold}Thesis Edges${utils_js_1.c.reset}`);
83
+ for (const e of edges.slice(0, 6)) {
84
+ const venue = e.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
85
+ const edgeColor = e.edge > 0 ? utils_js_1.c.green : utils_js_1.c.red;
86
+ console.log(` ${venue} ${(0, utils_js_1.rpad)(`${e.price}¢`, 5)} ${edgeColor}edge ${e.edge > 0 ? '+' : ''}${e.edge}¢${utils_js_1.c.reset} ${(0, utils_js_1.trunc)(e.title, 40)} ${utils_js_1.c.dim}${e.thesisSlug || ''}${utils_js_1.c.reset}`);
51
87
  }
52
88
  console.log();
53
89
  }
54
- if (data.signals?.length > 0) {
55
- console.log(`${utils_js_1.c.bold}Recent Signals${utils_js_1.c.reset}`);
56
- for (const s of data.signals.slice(0, 5)) {
57
- const d = s.confidenceDelta > 0 ? `${utils_js_1.c.green}▲${s.confidenceDelta}${utils_js_1.c.reset}` : s.confidenceDelta < 0 ? `${utils_js_1.c.red}▼${s.confidenceDelta}${utils_js_1.c.reset}` : `${s.confidenceDelta}`;
58
- console.log(` ${d} ${s.confidence}% ${s.summary.slice(0, 60)}`);
59
- console.log(` ${utils_js_1.c.dim}${s.thesisTitle.slice(0, 40)} ${(0, utils_js_1.shortDate)(s.evaluatedAt)}${utils_js_1.c.reset}`);
90
+ // ── Recent evaluations ─────────────────────────────────────────────────
91
+ const signals = data.signals || [];
92
+ if (signals.length > 0) {
93
+ console.log(`${utils_js_1.c.bold}Recent Evaluations${utils_js_1.c.reset}`);
94
+ for (const s of signals.slice(0, 5)) {
95
+ const d = s.confidenceDelta;
96
+ const dStr = d > 0 ? `${utils_js_1.c.green}+${d}%${utils_js_1.c.reset}` : d < 0 ? `${utils_js_1.c.red}${d}%${utils_js_1.c.reset}` : `${utils_js_1.c.dim}0%${utils_js_1.c.reset}`;
97
+ const ago = timeAgo(s.evaluatedAt);
98
+ console.log(` ${(0, utils_js_1.rpad)(ago, 5)} ${(0, utils_js_1.rpad)(s.thesisSlug || '?', 18)} ${(0, utils_js_1.rpad)(dStr, 14)} ${(0, utils_js_1.trunc)(s.summary, 50)}`);
60
99
  }
61
100
  console.log();
62
101
  }
63
- console.log(` ${utils_js_1.c.dim}Create a thesis for focused context: sf create "your market view"${utils_js_1.c.reset}\n`);
102
+ // ── Actions ────────────────────────────────────────────────────────────
103
+ console.log(`${utils_js_1.c.dim}${'─'.repeat(60)}${utils_js_1.c.reset}`);
104
+ console.log(` ${utils_js_1.c.cyan}sf query${utils_js_1.c.reset} "topic" ${utils_js_1.c.dim}ask anything${utils_js_1.c.reset}`);
105
+ console.log(` ${utils_js_1.c.cyan}sf scan${utils_js_1.c.reset} "keywords" ${utils_js_1.c.dim}search markets${utils_js_1.c.reset}`);
106
+ console.log(` ${utils_js_1.c.cyan}sf explore${utils_js_1.c.reset} ${utils_js_1.c.dim}public theses${utils_js_1.c.reset}`);
107
+ console.log(` ${utils_js_1.c.cyan}sf create${utils_js_1.c.reset} "thesis" ${utils_js_1.c.dim}start monitoring${utils_js_1.c.reset}`);
108
+ console.log();
64
109
  return;
65
110
  }
111
+ // ── Mode 2: Thesis-specific context (requires API key) ────────────────────
66
112
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
67
113
  const ctx = await client.getContext(id);
68
114
  if (opts.json) {
69
115
  console.log(JSON.stringify(ctx, null, 2));
70
116
  return;
71
117
  }
72
- // Context API shape:
73
- // { thesisId, thesis, title, status, confidence (number|null),
74
- // causalTree: { nodes: FlatNode[] }, edges: Edge[], edgeMeta,
75
- // lastEvaluation: { summary, confidenceDelta, ... }, updatedAt, ... }
76
- // Thesis header
77
118
  console.log(`\n${utils_js_1.c.bold}Thesis:${utils_js_1.c.reset} ${ctx.thesis || ctx.rawThesis || '(unknown)'}`);
78
119
  const confStr = ctx.confidence !== null && ctx.confidence !== undefined ? (0, utils_js_1.pct)(ctx.confidence) : '-';
79
120
  const confDelta = ctx.lastEvaluation?.confidenceDelta;
@@ -81,7 +122,7 @@ async function contextCommand(id, opts) {
81
122
  console.log(`${utils_js_1.c.bold}Confidence:${utils_js_1.c.reset} ${confStr}${deltaStr}`);
82
123
  console.log(`${utils_js_1.c.bold}Status:${utils_js_1.c.reset} ${ctx.status}`);
83
124
  console.log(`${utils_js_1.c.bold}Last Updated:${utils_js_1.c.reset} ${(0, utils_js_1.shortDate)(ctx.updatedAt)}`);
84
- // Causal tree nodes (flat array from API)
125
+ // Causal tree nodes
85
126
  const nodes = ctx.causalTree?.nodes;
86
127
  if (nodes && nodes.length > 0) {
87
128
  (0, utils_js_1.header)('Causal Tree');
@@ -92,53 +133,45 @@ async function contextCommand(id, opts) {
92
133
  console.log(`${indent}${utils_js_1.c.cyan}${node.id}${utils_js_1.c.reset} ${(0, utils_js_1.pad)(label, 40)} ${(0, utils_js_1.rpad)(prob, 5)}`);
93
134
  }
94
135
  }
95
- // Fetch positions if Kalshi is configured (local only, no server)
136
+ // Fetch positions if Kalshi is configured
96
137
  let positions = null;
97
138
  if ((0, kalshi_js_1.isKalshiConfigured)()) {
98
139
  try {
99
140
  positions = await (0, kalshi_js_1.getPositions)();
100
141
  }
101
- catch {
102
- // silently skip — positions are optional
103
- }
142
+ catch { /* optional */ }
104
143
  }
105
144
  const posMap = new Map();
106
145
  if (positions) {
107
- for (const p of positions) {
146
+ for (const p of positions)
108
147
  posMap.set(p.ticker, p);
109
- }
110
148
  }
111
- // Top edges (sorted by absolute edge size)
149
+ // Top edges
112
150
  const edges = ctx.edges;
113
151
  if (edges && edges.length > 0) {
114
- (0, utils_js_1.header)('Top Edges (by edge size)');
152
+ (0, utils_js_1.header)('Top Edges');
115
153
  const sorted = [...edges].sort((a, b) => Math.abs(b.edge ?? b.edgeSize ?? 0) - Math.abs(a.edge ?? a.edgeSize ?? 0));
116
154
  for (const edge of sorted.slice(0, 10)) {
117
155
  const edgeSize = edge.edge ?? edge.edgeSize ?? 0;
118
156
  const edgeColor = edgeSize > 10 ? utils_js_1.c.green : edgeSize > 0 ? utils_js_1.c.yellow : utils_js_1.c.red;
119
157
  const mktPrice = edge.marketPrice ?? edge.currentPrice ?? 0;
120
158
  const title = edge.market || edge.marketTitle || edge.marketId || '?';
121
- // Orderbook info (if enriched by rescan)
122
159
  const ob = edge.orderbook;
123
160
  const obStr = ob ? ` ${utils_js_1.c.dim}spread ${ob.spread}¢ ${ob.liquidityScore}${utils_js_1.c.reset}` : '';
124
- // Position overlay (if user has Kalshi positions)
125
161
  const pos = posMap.get(edge.marketId);
126
162
  let posStr = '';
127
163
  if (pos) {
128
164
  const pnl = pos.unrealized_pnl || 0;
129
165
  const pnlColor = pnl > 0 ? utils_js_1.c.green : pnl < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
130
166
  const pnlFmt = pnl >= 0 ? `+$${(pnl / 100).toFixed(0)}` : `-$${(Math.abs(pnl) / 100).toFixed(0)}`;
131
- posStr = ` ${utils_js_1.c.cyan}← ${pos.quantity}张 @ ${pos.average_price_paid}¢ ${pnlColor}${pnlFmt}${utils_js_1.c.reset}`;
167
+ posStr = ` ${utils_js_1.c.cyan}← ${pos.quantity}@${pos.average_price_paid}¢ ${pnlColor}${pnlFmt}${utils_js_1.c.reset}`;
132
168
  }
133
- console.log(` ${(0, utils_js_1.pad)(title, 35)}` +
134
- ` ${(0, utils_js_1.rpad)(mktPrice.toFixed(0) + '¢', 5)}` +
169
+ console.log(` ${(0, utils_js_1.pad)(title, 35)} ${(0, utils_js_1.rpad)(mktPrice.toFixed(0) + '¢', 5)}` +
135
170
  ` ${edgeColor}edge ${edgeSize > 0 ? '+' : ''}${edgeSize.toFixed(1)}${utils_js_1.c.reset}` +
136
- ` ${utils_js_1.c.dim}${edge.venue || ''}${utils_js_1.c.reset}` +
137
- obStr +
138
- posStr);
171
+ ` ${utils_js_1.c.dim}${edge.venue || ''}${utils_js_1.c.reset}` + obStr + posStr);
139
172
  }
140
173
  }
141
- // Last evaluation summary
174
+ // Last evaluation
142
175
  if (ctx.lastEvaluation?.summary) {
143
176
  (0, utils_js_1.header)('Last Evaluation');
144
177
  console.log(` ${utils_js_1.c.dim}${(0, utils_js_1.shortDate)(ctx.lastEvaluation.evaluatedAt)} | model: ${ctx.lastEvaluation.model || ''}${utils_js_1.c.reset}`);
@@ -151,9 +184,30 @@ async function contextCommand(id, opts) {
151
184
  }
152
185
  }
153
186
  }
154
- // Edge meta (last rescan time)
155
187
  if (ctx.edgeMeta?.lastRescanAt) {
156
188
  console.log(`\n${utils_js_1.c.dim}Last rescan: ${(0, utils_js_1.shortDate)(ctx.edgeMeta.lastRescanAt)}${utils_js_1.c.reset}`);
157
189
  }
158
190
  console.log('');
159
191
  }
192
+ /** Extract a short search query from a market title */
193
+ function extractQuery(title) {
194
+ return title
195
+ .replace(/^Will\s+/i, '')
196
+ .replace(/\?.*$/, '')
197
+ .replace(/by\s+\w+\s+\d{1,2}.*$/i, '')
198
+ .replace(/\*\*/g, '')
199
+ .trim()
200
+ .slice(0, 40)
201
+ .trim();
202
+ }
203
+ /** Human-readable time ago */
204
+ function timeAgo(iso) {
205
+ const ms = Date.now() - new Date(iso).getTime();
206
+ const min = Math.round(ms / 60000);
207
+ if (min < 60)
208
+ return `${min}m`;
209
+ const hr = Math.round(min / 60);
210
+ if (hr < 24)
211
+ return `${hr}h`;
212
+ return `${Math.round(hr / 24)}d`;
213
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * sf delta <thesisId> — Changes since a timestamp.
3
+ *
4
+ * Calls GET /api/thesis/:id/changes?since=<ts>
5
+ * Shows: confidence delta, new signals, node probability changes, edge movements.
6
+ */
7
+ interface DeltaOpts {
8
+ since?: string;
9
+ hours?: string;
10
+ json?: boolean;
11
+ watch?: boolean;
12
+ apiKey?: string;
13
+ apiUrl?: string;
14
+ }
15
+ export declare function deltaCommand(thesisId: string, opts: DeltaOpts): Promise<void>;
16
+ export {};
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * sf delta <thesisId> — Changes since a timestamp.
4
+ *
5
+ * Calls GET /api/thesis/:id/changes?since=<ts>
6
+ * Shows: confidence delta, new signals, node probability changes, edge movements.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.deltaCommand = deltaCommand;
10
+ const client_js_1 = require("../client.js");
11
+ const utils_js_1 = require("../utils.js");
12
+ async function deltaCommand(thesisId, opts) {
13
+ const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
14
+ // Resolve "since" — either explicit timestamp or --hours offset
15
+ const sinceDate = opts.since
16
+ ? new Date(opts.since)
17
+ : new Date(Date.now() - (parseInt(opts.hours || '6') * 3600000));
18
+ if (isNaN(sinceDate.getTime())) {
19
+ throw new Error(`Invalid timestamp: "${opts.since}". Use ISO 8601 format (e.g., 2026-03-28T14:00:00Z)`);
20
+ }
21
+ const run = async () => {
22
+ const since = sinceDate.toISOString();
23
+ const data = await client.getChanges(thesisId, since);
24
+ if (opts.json) {
25
+ console.log(JSON.stringify(data, null, 2));
26
+ return;
27
+ }
28
+ if (!data.changed) {
29
+ console.log(`${utils_js_1.c.dim}No changes since ${sinceDate.toLocaleString()}.${utils_js_1.c.reset}`);
30
+ return;
31
+ }
32
+ // Header
33
+ const id = (0, utils_js_1.shortId)(thesisId);
34
+ console.log();
35
+ console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Delta: ${id}${utils_js_1.c.reset}${utils_js_1.c.dim} — since ${sinceDate.toLocaleString()}${utils_js_1.c.reset}`);
36
+ console.log(`${utils_js_1.c.dim}${'─'.repeat(65)}${utils_js_1.c.reset}`);
37
+ // Confidence change
38
+ if (data.confidence !== undefined) {
39
+ const conf = Math.round(data.confidence * 100);
40
+ const prev = data.previousConfidence !== undefined ? Math.round(data.previousConfidence * 100) : null;
41
+ const delta = prev !== null ? conf - prev : null;
42
+ let deltaStr = '';
43
+ if (delta !== null) {
44
+ deltaStr = delta > 0 ? `${utils_js_1.c.green} (+${delta}%)${utils_js_1.c.reset}` : delta < 0 ? `${utils_js_1.c.red} (${delta}%)${utils_js_1.c.reset}` : `${utils_js_1.c.dim} (0%)${utils_js_1.c.reset}`;
45
+ }
46
+ console.log(` Confidence: ${utils_js_1.c.bold}${conf}%${utils_js_1.c.reset}${deltaStr}`);
47
+ }
48
+ // Evaluation count
49
+ if (data.evaluationCount) {
50
+ console.log(` Evaluations: ${data.evaluationCount} cycle(s)`);
51
+ }
52
+ // Updated nodes
53
+ const nodes = data.updatedNodes || [];
54
+ if (nodes.length > 0) {
55
+ console.log();
56
+ console.log(` ${utils_js_1.c.bold}Node Changes (${nodes.length}):${utils_js_1.c.reset}`);
57
+ for (const n of nodes) {
58
+ const prob = Math.round((n.newProbability ?? n.newProb ?? 0) * 100);
59
+ const prev = n.previousProbability ?? n.prevProb;
60
+ let changeStr = '';
61
+ if (prev !== undefined && prev !== null) {
62
+ const prevPct = Math.round(prev * 100);
63
+ const d = prob - prevPct;
64
+ changeStr = d > 0 ? ` ${utils_js_1.c.green}+${d}%${utils_js_1.c.reset}` : d < 0 ? ` ${utils_js_1.c.red}${d}%${utils_js_1.c.reset}` : '';
65
+ }
66
+ const label = n.label || n.nodeId || '?';
67
+ console.log(` ${label.slice(0, 40).padEnd(40)} ${prob}%${changeStr}`);
68
+ }
69
+ }
70
+ // New signals
71
+ const signals = data.newSignals || [];
72
+ if (signals.length > 0) {
73
+ console.log();
74
+ console.log(` ${utils_js_1.c.bold}New Signals (${signals.length}):${utils_js_1.c.reset}`);
75
+ for (const s of signals) {
76
+ const type = s.type || 'signal';
77
+ const content = (s.content || s.title || '').replace(/\n/g, ' ').slice(0, 70);
78
+ console.log(` ${utils_js_1.c.dim}[${type}]${utils_js_1.c.reset} ${content}`);
79
+ }
80
+ }
81
+ // Edge changes
82
+ const edges = data.edgeChanges || data.edges || [];
83
+ if (edges.length > 0) {
84
+ console.log();
85
+ console.log(` ${utils_js_1.c.bold}Edge Movements (${edges.length}):${utils_js_1.c.reset}`);
86
+ for (const e of edges.slice(0, 15)) {
87
+ const market = (e.market || e.marketId || '').slice(0, 35).padEnd(35);
88
+ const edge = e.edge ?? 0;
89
+ const prev = e.previousEdge ?? null;
90
+ const edgeStr = edge > 0 ? `+${edge}` : `${edge}`;
91
+ let changeStr = '';
92
+ if (prev !== null) {
93
+ const d = edge - prev;
94
+ changeStr = d > 0 ? ` ${utils_js_1.c.green}(+${d})${utils_js_1.c.reset}` : d < 0 ? ` ${utils_js_1.c.red}(${d})${utils_js_1.c.reset}` : '';
95
+ }
96
+ const color = edge > 0 ? utils_js_1.c.green : edge < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
97
+ console.log(` ${market} ${color}${edgeStr}${utils_js_1.c.reset}${changeStr}`);
98
+ }
99
+ }
100
+ console.log(`${utils_js_1.c.dim}${'─'.repeat(65)}${utils_js_1.c.reset}`);
101
+ console.log();
102
+ };
103
+ if (opts.watch) {
104
+ console.log(`${utils_js_1.c.dim}Watching for changes every 60s... (Ctrl+C to stop)${utils_js_1.c.reset}`);
105
+ // eslint-disable-next-line no-constant-condition
106
+ while (true) {
107
+ await run();
108
+ await new Promise(r => setTimeout(r, 60_000));
109
+ console.clear();
110
+ }
111
+ }
112
+ else {
113
+ await run();
114
+ }
115
+ }
@@ -14,6 +14,10 @@
14
14
  interface EdgesOpts {
15
15
  json?: boolean;
16
16
  limit?: string;
17
+ thesis?: string;
18
+ minEdge?: string;
19
+ minLiquidity?: string;
20
+ sort?: string;
17
21
  apiKey?: string;
18
22
  apiUrl?: string;
19
23
  }
@@ -20,17 +20,30 @@ const utils_js_1 = require("../utils.js");
20
20
  async function edgesCommand(opts) {
21
21
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
22
22
  const limit = parseInt(opts.limit || '20');
23
- // ── Step 1: Fetch all active theses ────────────────────────────────────────
24
- console.log(`${utils_js_1.c.dim}Fetching theses...${utils_js_1.c.reset}`);
25
- const data = await client.listTheses();
26
- const rawTheses = data.theses || data;
27
- const theses = (Array.isArray(rawTheses) ? rawTheses : []).filter((t) => t.status === 'active');
23
+ const minEdge = opts.minEdge ? parseInt(opts.minEdge) : 0;
24
+ const minLiq = opts.minLiquidity?.toLowerCase() || '';
25
+ const sortBy = opts.sort || 'edge';
26
+ // Support both letter grades (A/B/C/D) and word grades (high/medium/low)
27
+ const liqRank = { a: 4, high: 4, b: 3, medium: 3, c: 2, low: 2, d: 1 };
28
+ // ── Step 1: Fetch theses (or single thesis) ────────────────────────────────
29
+ let theses;
30
+ if (opts.thesis) {
31
+ // 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}`);
33
+ theses = [{ id: opts.thesis }];
34
+ }
35
+ else {
36
+ console.log(`${utils_js_1.c.dim}Fetching theses...${utils_js_1.c.reset}`);
37
+ const data = await client.listTheses();
38
+ const rawTheses = data.theses || data;
39
+ theses = (Array.isArray(rawTheses) ? rawTheses : []).filter((t) => t.status === 'active');
40
+ }
28
41
  if (theses.length === 0) {
29
42
  console.log(`${utils_js_1.c.yellow}No active theses found.${utils_js_1.c.reset} Create one: sf create "your thesis"`);
30
43
  return;
31
44
  }
32
45
  // ── Step 2: Fetch context for each thesis (parallel) ───────────────────────
33
- console.log(`${utils_js_1.c.dim}Fetching edges from ${theses.length} theses...${utils_js_1.c.reset}`);
46
+ console.log(`${utils_js_1.c.dim}Fetching edges from ${theses.length} ${theses.length === 1 ? 'thesis' : 'theses'}...${utils_js_1.c.reset}`);
34
47
  const allEdges = [];
35
48
  const contextPromises = theses.map(async (t) => {
36
49
  try {
@@ -76,6 +89,13 @@ async function edgesCommand(opts) {
76
89
  }
77
90
  }
78
91
  let merged = Array.from(deduped.values());
92
+ // ── Step 3b: Apply filters ─────────────────────────────────────────────────
93
+ if (minEdge > 0) {
94
+ merged = merged.filter(e => Math.abs(e.edge) >= minEdge);
95
+ }
96
+ if (minLiq && liqRank[minLiq]) {
97
+ merged = merged.filter(e => e.liquidityScore && (liqRank[e.liquidityScore.toLowerCase()] || 0) >= liqRank[minLiq]);
98
+ }
79
99
  // ── Step 4: Fetch positions (optional) ─────────────────────────────────────
80
100
  let positions = null;
81
101
  if ((0, kalshi_js_1.isKalshiConfigured)()) {
@@ -107,12 +127,18 @@ async function edgesCommand(opts) {
107
127
  }
108
128
  }
109
129
  }
110
- // ── Step 5: Sort by executableEdge (or edge) descending ────────────────────
111
- merged.sort((a, b) => {
112
- const aVal = a.executableEdge !== null ? a.executableEdge : a.edge;
113
- const bVal = b.executableEdge !== null ? b.executableEdge : b.edge;
114
- return Math.abs(bVal) - Math.abs(aVal);
115
- });
130
+ // ── Step 5: Sort ────────────────────────────────────────────────────────────
131
+ if (sortBy === 'spread') {
132
+ merged.sort((a, b) => (a.spread ?? 999) - (b.spread ?? 999));
133
+ }
134
+ else {
135
+ // Default: sort by executableEdge (or edge) descending
136
+ merged.sort((a, b) => {
137
+ const aVal = a.executableEdge !== null ? a.executableEdge : a.edge;
138
+ const bVal = b.executableEdge !== null ? b.executableEdge : b.edge;
139
+ return Math.abs(bVal) - Math.abs(aVal);
140
+ });
141
+ }
116
142
  // Apply limit
117
143
  const display = merged.slice(0, limit);
118
144
  // ── Step 6: JSON output ────────────────────────────────────────────────────
package/dist/index.js CHANGED
@@ -57,6 +57,7 @@ const book_js_1 = require("./commands/book.js");
57
57
  const prompt_js_1 = require("./commands/prompt.js");
58
58
  const augment_js_1 = require("./commands/augment.js");
59
59
  const telegram_js_1 = require("./commands/telegram.js");
60
+ const delta_js_1 = require("./commands/delta.js");
60
61
  const query_js_1 = require("./commands/query.js");
61
62
  const markets_js_1 = require("./commands/markets.js");
62
63
  const x_js_1 = require("./commands/x.js");
@@ -128,6 +129,7 @@ const GROUPED_HELP = `
128
129
 
129
130
  \x1b[1mInfo\x1b[22m
130
131
  \x1b[36mfeed\x1b[39m Evaluation history stream
132
+ \x1b[36mdelta\x1b[39m <id> Changes since timestamp
131
133
  \x1b[36mmilestones\x1b[39m Upcoming Kalshi events
132
134
  \x1b[36mschedule\x1b[39m Exchange status
133
135
  \x1b[36mannouncements\x1b[39m Exchange announcements
@@ -359,11 +361,19 @@ program
359
361
  .description('Top edges across all theses — what to trade now')
360
362
  .option('--json', 'JSON output for agents')
361
363
  .option('--limit <n>', 'Max edges to show', '20')
364
+ .option('--thesis <id>', 'Filter to a single thesis')
365
+ .option('--min-edge <cents>', 'Minimum absolute edge size in cents')
366
+ .option('--min-liquidity <grade>', 'Minimum liquidity (high/medium/low or A/B/C)')
367
+ .option('--sort <by>', 'Sort by: edge (default), spread')
362
368
  .action(async (opts, cmd) => {
363
369
  const g = cmd.optsWithGlobals();
364
370
  await run(() => (0, edges_js_1.edgesCommand)({
365
371
  json: opts.json,
366
372
  limit: opts.limit,
373
+ thesis: opts.thesis,
374
+ minEdge: opts.minEdge,
375
+ minLiquidity: opts.minLiquidity,
376
+ sort: opts.sort,
367
377
  apiKey: g.apiKey,
368
378
  apiUrl: g.apiUrl,
369
379
  }));
@@ -499,6 +509,18 @@ program
499
509
  const g = cmd.optsWithGlobals();
500
510
  await run(() => (0, feed_js_1.feedCommand)({ ...opts, apiKey: g.apiKey, apiUrl: g.apiUrl }));
501
511
  });
512
+ // ── sf delta <thesisId> ───────────────────────────────────────────────────────
513
+ program
514
+ .command('delta <thesisId>')
515
+ .description('Changes since a timestamp — confidence, nodes, signals, edges')
516
+ .option('--since <timestamp>', 'ISO 8601 start time (e.g., 2026-03-28T14:00:00Z)')
517
+ .option('--hours <n>', 'Hours to look back (default 6)', '6')
518
+ .option('--watch', 'Continuously poll every 60s')
519
+ .option('--json', 'JSON output')
520
+ .action(async (thesisId, opts, cmd) => {
521
+ const g = cmd.optsWithGlobals();
522
+ await run(() => (0, delta_js_1.deltaCommand)(thesisId, { ...opts, apiKey: g.apiKey, apiUrl: g.apiUrl }));
523
+ });
502
524
  // ── sf whatif <thesisId> ──────────────────────────────────────────────────────
503
525
  program
504
526
  .command('whatif <thesisId>')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.7.10",
3
+ "version": "1.7.11",
4
4
  "description": "Prediction market intelligence CLI. Causal thesis model, 24/7 Kalshi/Polymarket scan, live orderbook, edge detection. Interactive agent mode with tool calling.",
5
5
  "bin": {
6
6
  "sf": "./dist/index.js"