@spfunctions/cli 1.7.14 → 1.7.16
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 +332 -115
- package/package.json +1 -1
package/dist/commands/agent.js
CHANGED
|
@@ -219,21 +219,37 @@ function createFooterBar(piTui) {
|
|
|
219
219
|
exchangeOpen = null;
|
|
220
220
|
cachedWidth;
|
|
221
221
|
cachedLines;
|
|
222
|
+
isExplorer = false;
|
|
222
223
|
setFromContext(ctx, positions) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.
|
|
224
|
+
if (ctx._explorerMode) {
|
|
225
|
+
this.isExplorer = true;
|
|
226
|
+
this.thesisId = 'Explorer';
|
|
227
|
+
this.confidence = 0;
|
|
228
|
+
this.confidenceDelta = 0;
|
|
229
|
+
this.edgeCount = (ctx.edges || []).length;
|
|
230
|
+
const edges = ctx.edges || [];
|
|
231
|
+
if (edges.length > 0) {
|
|
232
|
+
const top = [...edges].sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))[0];
|
|
233
|
+
this.topEdge = `${(top.title || '').slice(0, 20)} +${Math.round(top.edge)}¢`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
this.isExplorer = false;
|
|
238
|
+
this.thesisId = (ctx.thesisId || '').slice(0, 8);
|
|
239
|
+
this.confidence = typeof ctx.confidence === 'number'
|
|
240
|
+
? Math.round(ctx.confidence * 100)
|
|
241
|
+
: (typeof ctx.confidence === 'string' ? Math.round(parseFloat(ctx.confidence) * 100) : 0);
|
|
242
|
+
this.confidenceDelta = ctx.lastEvaluation?.confidenceDelta
|
|
243
|
+
? Math.round(ctx.lastEvaluation.confidenceDelta * 100)
|
|
244
|
+
: 0;
|
|
245
|
+
this.edgeCount = (ctx.edges || []).length;
|
|
246
|
+
const edges = ctx.edges || [];
|
|
247
|
+
if (edges.length > 0) {
|
|
248
|
+
const top = [...edges].sort((a, b) => Math.abs(b.edge || b.edgeSize || 0) - Math.abs(a.edge || a.edgeSize || 0))[0];
|
|
249
|
+
const name = (top.market || top.marketTitle || top.marketId || '').slice(0, 20);
|
|
250
|
+
const edge = top.edge || top.edgeSize || 0;
|
|
251
|
+
this.topEdge = `${name} ${edge > 0 ? '+' : ''}${Math.round(edge)}\u00A2`;
|
|
252
|
+
}
|
|
237
253
|
}
|
|
238
254
|
if (positions && positions.length > 0) {
|
|
239
255
|
this.positionCount = positions.length;
|
|
@@ -260,23 +276,39 @@ function createFooterBar(piTui) {
|
|
|
260
276
|
if (this.cachedLines && this.cachedWidth === width)
|
|
261
277
|
return this.cachedLines;
|
|
262
278
|
this.cachedWidth = width;
|
|
263
|
-
// Line 1: thesis info
|
|
264
|
-
const id = C.emerald(this.thesisId);
|
|
265
|
-
const arrow = this.confidenceDelta > 0 ? '\u25B2' : this.confidenceDelta < 0 ? '\u25BC' : '\u2500';
|
|
266
|
-
const arrowColor = this.confidenceDelta > 0 ? C.emerald : this.confidenceDelta < 0 ? C.red : C.zinc600;
|
|
267
|
-
const deltaStr = this.confidenceDelta !== 0 ? ` (${this.confidenceDelta > 0 ? '+' : ''}${this.confidenceDelta})` : '';
|
|
268
|
-
const conf = arrowColor(`${arrow} ${this.confidence}%${deltaStr}`);
|
|
269
|
-
let pnl = '';
|
|
270
|
-
if (this.positionCount > 0) {
|
|
271
|
-
const pnlStr = this.pnlDollars >= 0
|
|
272
|
-
? C.emerald(`+$${this.pnlDollars.toFixed(2)}`)
|
|
273
|
-
: C.red(`-$${Math.abs(this.pnlDollars).toFixed(2)}`);
|
|
274
|
-
pnl = C.zinc600(`${this.positionCount} pos `) + pnlStr;
|
|
275
|
-
}
|
|
276
|
-
const edges = C.zinc600(`${this.edgeCount} edges`);
|
|
277
|
-
const top = this.topEdge ? C.zinc400(this.topEdge) : '';
|
|
279
|
+
// Line 1: thesis info (or explorer mode)
|
|
278
280
|
const sep = C.zinc600(' \u2502 ');
|
|
279
|
-
|
|
281
|
+
let line1Parts;
|
|
282
|
+
if (this.isExplorer) {
|
|
283
|
+
const id = C.emerald(bold('Explorer'));
|
|
284
|
+
const edges = C.zinc600(`${this.edgeCount} public edges`);
|
|
285
|
+
const top = this.topEdge ? C.zinc400(this.topEdge) : '';
|
|
286
|
+
let pnl = '';
|
|
287
|
+
if (this.positionCount > 0) {
|
|
288
|
+
const pnlStr = this.pnlDollars >= 0
|
|
289
|
+
? C.emerald(`+$${this.pnlDollars.toFixed(2)}`)
|
|
290
|
+
: C.red(`-$${Math.abs(this.pnlDollars).toFixed(2)}`);
|
|
291
|
+
pnl = C.zinc600(`${this.positionCount} pos `) + pnlStr;
|
|
292
|
+
}
|
|
293
|
+
line1Parts = [id, pnl, edges, top].filter(Boolean);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const id = C.emerald(this.thesisId);
|
|
297
|
+
const arrow = this.confidenceDelta > 0 ? '\u25B2' : this.confidenceDelta < 0 ? '\u25BC' : '\u2500';
|
|
298
|
+
const arrowColor = this.confidenceDelta > 0 ? C.emerald : this.confidenceDelta < 0 ? C.red : C.zinc600;
|
|
299
|
+
const deltaStr = this.confidenceDelta !== 0 ? ` (${this.confidenceDelta > 0 ? '+' : ''}${this.confidenceDelta})` : '';
|
|
300
|
+
const conf = arrowColor(`${arrow} ${this.confidence}%${deltaStr}`);
|
|
301
|
+
let pnl = '';
|
|
302
|
+
if (this.positionCount > 0) {
|
|
303
|
+
const pnlStr = this.pnlDollars >= 0
|
|
304
|
+
? C.emerald(`+$${this.pnlDollars.toFixed(2)}`)
|
|
305
|
+
: C.red(`-$${Math.abs(this.pnlDollars).toFixed(2)}`);
|
|
306
|
+
pnl = C.zinc600(`${this.positionCount} pos `) + pnlStr;
|
|
307
|
+
}
|
|
308
|
+
const edges = C.zinc600(`${this.edgeCount} edges`);
|
|
309
|
+
const top = this.topEdge ? C.zinc400(this.topEdge) : '';
|
|
310
|
+
line1Parts = [id, conf, pnl, edges, top].filter(Boolean);
|
|
311
|
+
}
|
|
280
312
|
let line1 = C.bgZinc800(' ' + truncateToWidth(line1Parts.join(sep), width - 2, '') + ' ');
|
|
281
313
|
const l1vw = visibleWidth(line1);
|
|
282
314
|
if (l1vw < width)
|
|
@@ -393,14 +425,18 @@ function renderPositions(positions) {
|
|
|
393
425
|
return lines.join('\n');
|
|
394
426
|
}
|
|
395
427
|
// ─── Thesis selector (arrow keys + enter, like Claude Code) ─────────────────
|
|
396
|
-
async function selectThesis(theses) {
|
|
428
|
+
async function selectThesis(theses, includeExplorer = false) {
|
|
397
429
|
return new Promise((resolve) => {
|
|
398
430
|
let selected = 0;
|
|
399
|
-
const items =
|
|
431
|
+
const items = [];
|
|
432
|
+
if (includeExplorer) {
|
|
433
|
+
items.push({ id: '_explorer', conf: -1, title: 'Explorer mode — no thesis, full market access' });
|
|
434
|
+
}
|
|
435
|
+
for (const t of theses) {
|
|
400
436
|
const conf = typeof t.confidence === 'number' ? Math.round(t.confidence * 100) : 0;
|
|
401
437
|
const title = (t.rawThesis || t.thesis || t.title || '').slice(0, 55);
|
|
402
|
-
|
|
403
|
-
}
|
|
438
|
+
items.push({ id: t.id, conf, title });
|
|
439
|
+
}
|
|
404
440
|
const write = process.stdout.write.bind(process.stdout);
|
|
405
441
|
// Use alternate screen buffer for clean rendering (like Claude Code)
|
|
406
442
|
write('\x1b[?1049h'); // enter alternate screen
|
|
@@ -412,10 +448,16 @@ async function selectThesis(theses) {
|
|
|
412
448
|
const item = items[i];
|
|
413
449
|
const sel = i === selected;
|
|
414
450
|
const cursor = sel ? '\x1b[38;2;16;185;129m › \x1b[39m' : ' ';
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
451
|
+
if (item.id === '_explorer') {
|
|
452
|
+
const title = sel ? `\x1b[38;2;16;185;129m${item.title}\x1b[39m` : `\x1b[38;2;80;80;88m${item.title}\x1b[39m`;
|
|
453
|
+
write(`${cursor}${title}\n`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const id = sel ? `\x1b[38;2;16;185;129m${item.id.slice(0, 8)}\x1b[39m` : `\x1b[38;2;55;55;60m${item.id.slice(0, 8)}\x1b[39m`;
|
|
457
|
+
const conf = `\x1b[38;2;55;55;60m${item.conf}%\x1b[39m`;
|
|
458
|
+
const title = sel ? `\x1b[38;2;160;160;165m${item.title}\x1b[39m` : `\x1b[38;2;80;80;88m${item.title}\x1b[39m`;
|
|
459
|
+
write(`${cursor}${id} ${conf} ${title}\n`);
|
|
460
|
+
}
|
|
419
461
|
}
|
|
420
462
|
write(`\n \x1b[38;2;55;55;60m↑↓ navigate · enter select\x1b[39m`);
|
|
421
463
|
}
|
|
@@ -507,36 +549,35 @@ async function agentCommand(thesisId, opts) {
|
|
|
507
549
|
}
|
|
508
550
|
const sfClient = new client_js_1.SFClient();
|
|
509
551
|
// ── Resolve thesis ID (interactive selection if needed) ─────────────────────
|
|
510
|
-
let resolvedThesisId = thesisId;
|
|
552
|
+
let resolvedThesisId = thesisId || null;
|
|
553
|
+
let explorerMode = false;
|
|
511
554
|
if (!resolvedThesisId) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
555
|
+
let active = [];
|
|
556
|
+
try {
|
|
557
|
+
const data = await sfClient.listTheses();
|
|
558
|
+
const theses = (data.theses || data);
|
|
559
|
+
active = theses.filter((t) => t.status === 'active');
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
// No API key or network error — explorer mode
|
|
563
|
+
active = [];
|
|
564
|
+
}
|
|
515
565
|
if (active.length === 0) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
process.exit(1);
|
|
519
|
-
}
|
|
520
|
-
// No theses — offer to create one
|
|
521
|
-
console.log('\n No active theses found.\n');
|
|
522
|
-
const readline = await import('readline');
|
|
523
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
524
|
-
const answer = await new Promise(resolve => rl.question(' Enter a thesis to create (or press Enter to exit):\n > ', resolve));
|
|
525
|
-
rl.close();
|
|
526
|
-
if (!answer.trim()) {
|
|
527
|
-
process.exit(0);
|
|
528
|
-
}
|
|
529
|
-
console.log('\n Creating thesis...\n');
|
|
530
|
-
const result = await sfClient.createThesis(answer.trim(), true);
|
|
531
|
-
resolvedThesisId = result.id;
|
|
532
|
-
console.log(` ✓ Created: ${result.id?.slice(0, 8)}\n`);
|
|
566
|
+
// No theses — go straight to explorer mode
|
|
567
|
+
explorerMode = true;
|
|
533
568
|
}
|
|
534
569
|
else if (active.length === 1) {
|
|
535
570
|
resolvedThesisId = active[0].id;
|
|
536
571
|
}
|
|
537
572
|
else if (process.stdin.isTTY && !opts?.noTui) {
|
|
538
|
-
// Multiple theses — interactive
|
|
539
|
-
|
|
573
|
+
// Multiple theses — interactive selector with explorer option at top
|
|
574
|
+
const selected = await selectThesis(active, true);
|
|
575
|
+
if (selected === '_explorer') {
|
|
576
|
+
explorerMode = true;
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
resolvedThesisId = selected;
|
|
580
|
+
}
|
|
540
581
|
}
|
|
541
582
|
else {
|
|
542
583
|
// Non-interactive (--plain, telegram, piped) — use first active
|
|
@@ -544,10 +585,18 @@ async function agentCommand(thesisId, opts) {
|
|
|
544
585
|
}
|
|
545
586
|
}
|
|
546
587
|
// ── Fetch initial context ──────────────────────────────────────────────────
|
|
547
|
-
let latestContext
|
|
588
|
+
let latestContext;
|
|
589
|
+
if (explorerMode) {
|
|
590
|
+
const { fetchGlobalContext } = await import('../client.js');
|
|
591
|
+
latestContext = await fetchGlobalContext();
|
|
592
|
+
latestContext._explorerMode = true;
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
596
|
+
}
|
|
548
597
|
// ── Branch: plain-text mode ────────────────────────────────────────────────
|
|
549
598
|
if (opts?.noTui) {
|
|
550
|
-
return runPlainTextAgent({ openrouterKey, sfClient, resolvedThesisId: resolvedThesisId, latestContext, useProxy, llmBaseUrl, sfApiKey, sfApiUrl, opts });
|
|
599
|
+
return runPlainTextAgent({ openrouterKey, sfClient, resolvedThesisId: resolvedThesisId || '_explorer', latestContext, useProxy, llmBaseUrl, sfApiKey, sfApiUrl, opts });
|
|
551
600
|
}
|
|
552
601
|
// ── Dynamic imports (all ESM-only packages) ────────────────────────────────
|
|
553
602
|
const piTui = await import('@mariozechner/pi-tui');
|
|
@@ -1399,7 +1448,7 @@ async function agentCommand(thesisId, opts) {
|
|
|
1399
1448
|
{
|
|
1400
1449
|
name: 'create_thesis',
|
|
1401
1450
|
label: 'Create Thesis',
|
|
1402
|
-
description: 'Create a new thesis from a raw thesis statement. Returns the thesis ID, confidence, node count, and edge count.',
|
|
1451
|
+
description: 'Create a new thesis from a raw thesis statement. Returns the thesis ID, confidence, node count, and edge count. In explorer mode, this automatically transitions to thesis mode.',
|
|
1403
1452
|
parameters: Type.Object({
|
|
1404
1453
|
rawThesis: Type.String({ description: 'The raw thesis statement to create' }),
|
|
1405
1454
|
webhookUrl: Type.Optional(Type.String({ description: 'Optional webhook URL for notifications' })),
|
|
@@ -1410,8 +1459,21 @@ async function agentCommand(thesisId, opts) {
|
|
|
1410
1459
|
const nodeCount = thesis.causalTree?.nodes?.length || 0;
|
|
1411
1460
|
const edgeCount = (thesis.edges || []).length;
|
|
1412
1461
|
const confidence = typeof thesis.confidence === 'number' ? Math.round(thesis.confidence * 100) : 0;
|
|
1462
|
+
// ── Auto-transition from explorer to thesis mode ──────────────────
|
|
1463
|
+
if (explorerMode && thesis.id) {
|
|
1464
|
+
explorerMode = false;
|
|
1465
|
+
resolvedThesisId = thesis.id;
|
|
1466
|
+
try {
|
|
1467
|
+
latestContext = await sfClient.getContext(thesis.id);
|
|
1468
|
+
const newPrompt = buildSystemPrompt(latestContext);
|
|
1469
|
+
agent.setSystemPrompt(newPrompt);
|
|
1470
|
+
footerBar.setFromContext(latestContext, initialPositions || undefined);
|
|
1471
|
+
tui.requestRender();
|
|
1472
|
+
}
|
|
1473
|
+
catch { /* context fetch failed, still switch */ }
|
|
1474
|
+
}
|
|
1413
1475
|
return {
|
|
1414
|
-
content: [{ type: 'text', text: `Thesis created.\nID: ${thesis.id}\nConfidence: ${confidence}%\nNodes: ${nodeCount}\nEdges: ${edgeCount}
|
|
1476
|
+
content: [{ type: 'text', text: `Thesis created.\nID: ${thesis.id}\nConfidence: ${confidence}%\nNodes: ${nodeCount}\nEdges: ${edgeCount}\n\nHeartbeat engine is now monitoring this thesis 24/7. Use /switch ${thesis.id?.slice(0, 8)} to focus on it.` }],
|
|
1415
1477
|
details: {},
|
|
1416
1478
|
};
|
|
1417
1479
|
},
|
|
@@ -1811,7 +1873,58 @@ ${edgesSummary}
|
|
|
1811
1873
|
|
|
1812
1874
|
${ctx.lastEvaluation?.summary ? `Latest evaluation summary: ${ctx.lastEvaluation.summary.slice(0, 300)}` : ''}`;
|
|
1813
1875
|
}
|
|
1814
|
-
|
|
1876
|
+
function buildExplorerPrompt(ctx) {
|
|
1877
|
+
const config = (0, config_js_1.loadConfig)();
|
|
1878
|
+
const theseCount = ctx.theses?.length || 0;
|
|
1879
|
+
const edgeCount = ctx.edges?.length || 0;
|
|
1880
|
+
const topEdges = (ctx.edges || [])
|
|
1881
|
+
.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
|
|
1882
|
+
.slice(0, 5)
|
|
1883
|
+
.map((e) => ` ${(e.title || '').slice(0, 40)} | ${e.venue || 'kalshi'} | mkt ${e.price}¢ | edge +${e.edge}`)
|
|
1884
|
+
.join('\n') || ' (no edges)';
|
|
1885
|
+
return `You are a prediction market research assistant with access to live data across Kalshi, Polymarket, X/Twitter, and traditional markets.
|
|
1886
|
+
|
|
1887
|
+
You are in EXPLORER MODE — not bound to any specific thesis. Help the user research, compare, and understand prediction market data across all sources.
|
|
1888
|
+
|
|
1889
|
+
## What you can do
|
|
1890
|
+
- Search and compare markets across Kalshi and Polymarket (scan_markets)
|
|
1891
|
+
- Answer questions with live market data + LLM synthesis (query)
|
|
1892
|
+
- Check traditional market prices — SPY, VIX, gold, oil, bonds (get_markets)
|
|
1893
|
+
- Browse public theses and their edges (explore_public)
|
|
1894
|
+
- Search X/Twitter for sentiment and breaking news (search_x, x_volume, x_news)
|
|
1895
|
+
- Check orderbook depth and liquidity (inspect_book, get_liquidity)
|
|
1896
|
+
- View user positions across venues (get_positions)
|
|
1897
|
+
- Create a new thesis when the user forms a view (create_thesis)
|
|
1898
|
+
|
|
1899
|
+
## CRITICAL: Thesis creation transition
|
|
1900
|
+
When the user expresses a market view worth tracking — explicitly ("create a thesis") or implicitly ("I think oil stays above $100", "the war won't end soon") — use create_thesis to create it. After creation, tell the user: "Thesis created. The heartbeat engine is now monitoring this 24/7. Use /switch <id> to focus on it."
|
|
1901
|
+
|
|
1902
|
+
## Your analytical framework
|
|
1903
|
+
Edge = thesis price - market price. Positive = market underprices.
|
|
1904
|
+
Edge types: "consensus_gap" (real disagreement), "attention_gap" (no real pricing), "timing_gap" (market lags), "risk_premium" (settlement risk).
|
|
1905
|
+
Price reliability: depth >= 500 = consensus. depth < 100 = unreliable. spread > 5¢ = noisy.
|
|
1906
|
+
Always state contract expiry and next catalyst. No catalyst = capital lock risk.
|
|
1907
|
+
|
|
1908
|
+
## Your behavioral rules
|
|
1909
|
+
- Be concise. Use tools for fresh data. Don't guess prices.
|
|
1910
|
+
- You do NOT know the user's positions at start. Call get_positions before discussing trades.
|
|
1911
|
+
- If user mentions news, offer to create a thesis or inject a signal if one exists.
|
|
1912
|
+
- Don't end with "anything else?"
|
|
1913
|
+
- Use Chinese if user writes Chinese, English if English.
|
|
1914
|
+
- Prices in cents (¢). P&L in dollars ($).
|
|
1915
|
+
|
|
1916
|
+
## Trading status
|
|
1917
|
+
${config.tradingEnabled ? 'Trading is ENABLED.' : 'Trading is DISABLED. Tell user: sf setup --enable-trading'}
|
|
1918
|
+
|
|
1919
|
+
## Current market snapshot
|
|
1920
|
+
Public theses tracked: ${theseCount}
|
|
1921
|
+
Top edges across all public theses:
|
|
1922
|
+
${topEdges}
|
|
1923
|
+
`;
|
|
1924
|
+
}
|
|
1925
|
+
const systemPrompt = explorerMode
|
|
1926
|
+
? buildExplorerPrompt(latestContext)
|
|
1927
|
+
: buildSystemPrompt(latestContext);
|
|
1815
1928
|
// ── Create Agent ───────────────────────────────────────────────────────────
|
|
1816
1929
|
const agent = new Agent({
|
|
1817
1930
|
initialState: {
|
|
@@ -1830,7 +1943,7 @@ ${ctx.lastEvaluation?.summary ? `Latest evaluation summary: ${ctx.lastEvaluation
|
|
|
1830
1943
|
// ── Session restore ────────────────────────────────────────────────────────
|
|
1831
1944
|
let sessionRestored = false;
|
|
1832
1945
|
if (!opts?.newSession) {
|
|
1833
|
-
const saved = loadSession(resolvedThesisId);
|
|
1946
|
+
const saved = loadSession(resolvedThesisId || '_explorer');
|
|
1834
1947
|
if (saved?.messages?.length > 0) {
|
|
1835
1948
|
try {
|
|
1836
1949
|
// Clean corrupted messages: empty content, missing role, broken alternation
|
|
@@ -1871,7 +1984,7 @@ ${ctx.lastEvaluation?.summary ? `Latest evaluation summary: ${ctx.lastEvaluation
|
|
|
1871
1984
|
try {
|
|
1872
1985
|
const msgs = agent.state.messages;
|
|
1873
1986
|
if (msgs.length > 0) {
|
|
1874
|
-
saveSession(resolvedThesisId, currentModelName, msgs);
|
|
1987
|
+
saveSession(resolvedThesisId || '_explorer', currentModelName, msgs);
|
|
1875
1988
|
}
|
|
1876
1989
|
}
|
|
1877
1990
|
catch { /* best-effort save */ }
|
|
@@ -2015,29 +2128,58 @@ ${ctx.lastEvaluation?.summary ? `Latest evaluation summary: ${ctx.lastEvaluation
|
|
|
2015
2128
|
}
|
|
2016
2129
|
case '/tree': {
|
|
2017
2130
|
addSpacer();
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
2021
|
-
addSystemText(C.zinc200(bold('Causal Tree')) + '\n' + renderCausalTree(latestContext, piTui));
|
|
2131
|
+
if (explorerMode) {
|
|
2132
|
+
addSystemText(C.zinc400('No thesis selected. Use /switch <id> to pick one, or ask me to create one.'));
|
|
2022
2133
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2134
|
+
else {
|
|
2135
|
+
try {
|
|
2136
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
2137
|
+
addSystemText(C.zinc200(bold('Causal Tree')) + '\n' + renderCausalTree(latestContext, piTui));
|
|
2138
|
+
}
|
|
2139
|
+
catch (err) {
|
|
2140
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
2141
|
+
}
|
|
2025
2142
|
}
|
|
2026
2143
|
addSpacer();
|
|
2027
2144
|
return true;
|
|
2028
2145
|
}
|
|
2029
2146
|
case '/edges': {
|
|
2030
2147
|
addSpacer();
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2148
|
+
if (explorerMode) {
|
|
2149
|
+
// Show global public edges
|
|
2150
|
+
try {
|
|
2151
|
+
const { fetchGlobalContext } = await import('../client.js');
|
|
2152
|
+
const global = await fetchGlobalContext();
|
|
2153
|
+
const edges = (global.edges || []).sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge)).slice(0, 10);
|
|
2154
|
+
if (edges.length === 0) {
|
|
2155
|
+
addSystemText(C.zinc400('No public edges available.'));
|
|
2156
|
+
}
|
|
2157
|
+
else {
|
|
2158
|
+
const lines = edges.map((e) => {
|
|
2159
|
+
const name = (e.title || '').slice(0, 35).padEnd(35);
|
|
2160
|
+
const venue = (e.venue || 'kalshi').padEnd(5);
|
|
2161
|
+
const mkt = String(Math.round(e.price || 0)).padStart(3) + '¢';
|
|
2162
|
+
const edge = '+' + Math.round(e.edge || 0);
|
|
2163
|
+
return ` ${C.zinc400(name)} ${C.zinc600(venue)} ${C.zinc400(mkt)} ${C.emerald(edge.padStart(4))}`;
|
|
2164
|
+
}).join('\n');
|
|
2165
|
+
addSystemText(C.zinc200(bold('Public Edges')) + '\n' + lines);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
catch (err) {
|
|
2169
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
2036
2170
|
}
|
|
2037
|
-
addSystemText(C.zinc200(bold('Edges')) + '\n' + renderEdges(latestContext, piTui));
|
|
2038
2171
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2172
|
+
else {
|
|
2173
|
+
try {
|
|
2174
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
2175
|
+
if (cachedPositions) {
|
|
2176
|
+
latestContext._positions = cachedPositions;
|
|
2177
|
+
}
|
|
2178
|
+
addSystemText(C.zinc200(bold('Edges')) + '\n' + renderEdges(latestContext, piTui));
|
|
2179
|
+
}
|
|
2180
|
+
catch (err) {
|
|
2181
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
2182
|
+
}
|
|
2041
2183
|
}
|
|
2042
2184
|
addSpacer();
|
|
2043
2185
|
return true;
|
|
@@ -2068,6 +2210,11 @@ ${ctx.lastEvaluation?.summary ? `Latest evaluation summary: ${ctx.lastEvaluation
|
|
|
2068
2210
|
}
|
|
2069
2211
|
case '/eval': {
|
|
2070
2212
|
addSpacer();
|
|
2213
|
+
if (explorerMode) {
|
|
2214
|
+
addSystemText(C.zinc400('No thesis selected. Use /switch <id> to pick one, or ask me to create one.'));
|
|
2215
|
+
addSpacer();
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2071
2218
|
addSystemText(C.zinc600('Triggering evaluation...'));
|
|
2072
2219
|
tui.requestRender();
|
|
2073
2220
|
try {
|
|
@@ -2533,6 +2680,34 @@ Output a structured summary. Be concise but preserve every important detail —
|
|
|
2533
2680
|
// ── Welcome dashboard builder ────────────────────────────────────────────
|
|
2534
2681
|
function buildWelcomeDashboard(ctx, positions) {
|
|
2535
2682
|
const lines = [];
|
|
2683
|
+
// ── Explorer mode welcome ──────────────────────────────────────────────
|
|
2684
|
+
if (ctx._explorerMode) {
|
|
2685
|
+
const edgeCount = ctx.edges?.length || 0;
|
|
2686
|
+
const theseCount = ctx.theses?.length || 0;
|
|
2687
|
+
lines.push(C.zinc600('\u2500'.repeat(55)));
|
|
2688
|
+
lines.push(' ' + C.emerald(bold('Explorer mode')) + C.zinc600(' — full market access, no thesis'));
|
|
2689
|
+
lines.push(' ' + C.zinc600(`${theseCount} public theses \u2502 ${edgeCount} edges tracked`));
|
|
2690
|
+
lines.push(C.zinc600('\u2500'.repeat(55)));
|
|
2691
|
+
// Show top public edges
|
|
2692
|
+
const edges = ctx.edges || [];
|
|
2693
|
+
if (edges.length > 0) {
|
|
2694
|
+
const sorted = [...edges].sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge)).slice(0, 5);
|
|
2695
|
+
lines.push(' ' + C.zinc400(bold('TOP PUBLIC EDGES')) + C.zinc600(' mkt edge'));
|
|
2696
|
+
for (const e of sorted) {
|
|
2697
|
+
const name = (e.title || '').slice(0, 30).padEnd(30);
|
|
2698
|
+
const mkt = String(Math.round(e.price || 0)).padStart(3) + '\u00A2';
|
|
2699
|
+
const edge = e.edge || 0;
|
|
2700
|
+
const edgeStr = '+' + Math.round(edge);
|
|
2701
|
+
const edgeColor = Math.abs(edge) >= 15 ? C.emerald : Math.abs(edge) >= 8 ? C.amber : C.zinc400;
|
|
2702
|
+
lines.push(` ${C.zinc400(name)} ${C.zinc400(mkt)} ${edgeColor(edgeStr.padStart(4))}`);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
lines.push(C.zinc600('\u2500'.repeat(55)));
|
|
2706
|
+
lines.push(' ' + C.zinc600('Ask anything, or describe a view to create a thesis.'));
|
|
2707
|
+
lines.push(C.zinc600('\u2500'.repeat(55)));
|
|
2708
|
+
return lines.join('\n');
|
|
2709
|
+
}
|
|
2710
|
+
// ── Thesis mode welcome (existing) ────────────────────────────────────
|
|
2536
2711
|
const thesisText = ctx.thesis || ctx.rawThesis || 'N/A';
|
|
2537
2712
|
const truncated = thesisText.length > 100 ? thesisText.slice(0, 100) + '...' : thesisText;
|
|
2538
2713
|
const conf = typeof ctx.confidence === 'number'
|
|
@@ -2630,25 +2805,26 @@ Output a structured summary. Be concise but preserve every important detail —
|
|
|
2630
2805
|
}
|
|
2631
2806
|
// absDelta === 0: truly nothing changed, stay silent
|
|
2632
2807
|
}
|
|
2633
|
-
// ── Start heartbeat polling
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2808
|
+
// ── Start heartbeat polling (thesis mode only) ──────────────────────────
|
|
2809
|
+
if (!explorerMode)
|
|
2810
|
+
heartbeatPollTimer = setInterval(async () => {
|
|
2811
|
+
try {
|
|
2812
|
+
const delta = await sfClient.getChanges(resolvedThesisId, lastPollTimestamp);
|
|
2813
|
+
lastPollTimestamp = new Date().toISOString();
|
|
2814
|
+
if (!delta.changed)
|
|
2815
|
+
return;
|
|
2816
|
+
if (isProcessing || pendingPrompt) {
|
|
2817
|
+
// Agent is busy — queue for delivery after agent_end
|
|
2818
|
+
pendingHeartbeatDelta = delta;
|
|
2819
|
+
}
|
|
2820
|
+
else {
|
|
2821
|
+
handleHeartbeatDelta(delta);
|
|
2822
|
+
}
|
|
2643
2823
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2824
|
+
catch {
|
|
2825
|
+
// Silent — don't spam errors from background polling
|
|
2646
2826
|
}
|
|
2647
|
-
}
|
|
2648
|
-
catch {
|
|
2649
|
-
// Silent — don't spam errors from background polling
|
|
2650
|
-
}
|
|
2651
|
-
}, 60_000); // every 60 seconds
|
|
2827
|
+
}, 60_000); // every 60 seconds
|
|
2652
2828
|
// ── Start TUI ──────────────────────────────────────────────────────────────
|
|
2653
2829
|
tui.start();
|
|
2654
2830
|
}
|
|
@@ -3487,17 +3663,49 @@ async function runPlainTextAgent(params) {
|
|
|
3487
3663
|
}
|
|
3488
3664
|
// ── System prompt ─────────────────────────────────────────────────────────
|
|
3489
3665
|
const ctx = latestContext;
|
|
3490
|
-
const
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3666
|
+
const isExplorerPlain = ctx._explorerMode || resolvedThesisId === '_explorer';
|
|
3667
|
+
let systemPrompt;
|
|
3668
|
+
if (isExplorerPlain) {
|
|
3669
|
+
const topEdges = (ctx.edges || [])
|
|
3670
|
+
.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
|
|
3671
|
+
.slice(0, 5)
|
|
3672
|
+
.map((e) => ` ${(e.title || '').slice(0, 40)} | ${e.venue || 'kalshi'} | +${e.edge}`)
|
|
3673
|
+
.join('\n') || ' (no edges)';
|
|
3674
|
+
systemPrompt = `You are a prediction market research assistant in EXPLORER MODE — not bound to any thesis.
|
|
3675
|
+
|
|
3676
|
+
## What you can do
|
|
3677
|
+
- query: LLM-enhanced market search
|
|
3678
|
+
- scan_markets: search Kalshi + Polymarket
|
|
3679
|
+
- get_markets: traditional markets (SPY, VIX, gold, oil)
|
|
3680
|
+
- explore_public: browse public theses
|
|
3681
|
+
- search_x, x_volume, x_news: X/Twitter signals
|
|
3682
|
+
- get_positions: portfolio positions
|
|
3683
|
+
- create_thesis: create a thesis when user forms a view
|
|
3684
|
+
|
|
3685
|
+
## CRITICAL: When the user expresses a view worth tracking, use create_thesis. After creation, confirm and continue with the new thesis context.
|
|
3686
|
+
|
|
3687
|
+
## Rules
|
|
3688
|
+
- Be concise. Use tools for fresh data.
|
|
3689
|
+
- Use Chinese if user writes Chinese, English if English.
|
|
3690
|
+
- Prices in cents (¢). P&L in dollars ($).
|
|
3691
|
+
${config.tradingEnabled ? '- Trading ENABLED.' : '- Trading DISABLED.'}
|
|
3692
|
+
|
|
3693
|
+
## Market snapshot
|
|
3694
|
+
Public edges:
|
|
3695
|
+
${topEdges}`;
|
|
3696
|
+
}
|
|
3697
|
+
else {
|
|
3698
|
+
const edgesSummary = ctx.edges
|
|
3699
|
+
?.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
|
|
3700
|
+
.slice(0, 5)
|
|
3701
|
+
.map((e) => ` ${(e.market || '').slice(0, 40)} | ${e.venue || 'kalshi'} | mkt ${e.marketPrice}\u00A2 | edge ${e.edge > 0 ? '+' : ''}${e.edge}`)
|
|
3702
|
+
.join('\n') || ' (no edges)';
|
|
3703
|
+
const nodesSummary = ctx.causalTree?.nodes
|
|
3704
|
+
?.filter((n) => n.depth === 0)
|
|
3705
|
+
.map((n) => ` ${n.id} ${(n.label || '').slice(0, 40)} \u2014 ${Math.round(n.probability * 100)}%`)
|
|
3706
|
+
.join('\n') || ' (no causal tree)';
|
|
3707
|
+
const conf = typeof ctx.confidence === 'number' ? Math.round(ctx.confidence * 100) : 0;
|
|
3708
|
+
systemPrompt = `You are a prediction market trading assistant. Your job is not to please the user — it is to help them see reality clearly and make correct trading decisions.
|
|
3501
3709
|
|
|
3502
3710
|
## Framework
|
|
3503
3711
|
Edge = thesis price - market price. Positive = market underprices. executableEdge = edge minus spread.
|
|
@@ -3540,6 +3748,7 @@ Top edges:
|
|
|
3540
3748
|
${edgesSummary}
|
|
3541
3749
|
|
|
3542
3750
|
${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice(0, 300)}` : ''}`;
|
|
3751
|
+
}
|
|
3543
3752
|
// ── Create agent ──────────────────────────────────────────────────────────
|
|
3544
3753
|
const agent = new Agent({
|
|
3545
3754
|
initialState: { systemPrompt, model, tools, thinkingLevel: 'off' },
|
|
@@ -3582,11 +3791,19 @@ ${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice
|
|
|
3582
3791
|
}
|
|
3583
3792
|
});
|
|
3584
3793
|
// ── Welcome ───────────────────────────────────────────────────────────────
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3794
|
+
if (isExplorerPlain) {
|
|
3795
|
+
console.log(`SF Agent — Explorer mode | ${currentModelName}`);
|
|
3796
|
+
console.log(`Public edges: ${(ctx.edges || []).length}`);
|
|
3797
|
+
console.log('Ask anything about prediction markets. Type /help for commands, /exit to quit.\n');
|
|
3798
|
+
}
|
|
3799
|
+
else {
|
|
3800
|
+
const thesisText = ctx.thesis || ctx.rawThesis || 'N/A';
|
|
3801
|
+
const plainConf = typeof ctx.confidence === 'number' ? Math.round(ctx.confidence * 100) : 0;
|
|
3802
|
+
console.log(`SF Agent — ${resolvedThesisId.slice(0, 8)} | ${plainConf}% | ${currentModelName}`);
|
|
3803
|
+
console.log(`Thesis: ${thesisText.length > 100 ? thesisText.slice(0, 100) + '...' : thesisText}`);
|
|
3804
|
+
console.log(`Edges: ${(ctx.edges || []).length} | Status: ${ctx.status}`);
|
|
3805
|
+
console.log('Type /help for commands, /exit to quit.\n');
|
|
3806
|
+
}
|
|
3590
3807
|
// ── REPL loop ─────────────────────────────────────────────────────────────
|
|
3591
3808
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '> ' });
|
|
3592
3809
|
rl.prompt();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfunctions/cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.16",
|
|
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"
|