@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/agent.js
CHANGED
|
@@ -26,6 +26,7 @@ const kalshi_js_1 = require("../kalshi.js");
|
|
|
26
26
|
const polymarket_js_1 = require("../polymarket.js");
|
|
27
27
|
const topics_js_1 = require("../topics.js");
|
|
28
28
|
const config_js_1 = require("../config.js");
|
|
29
|
+
const loader_js_1 = require("../skills/loader.js");
|
|
29
30
|
// ─── Session persistence ─────────────────────────────────────────────────────
|
|
30
31
|
function getSessionDir() {
|
|
31
32
|
return path_1.default.join(os_1.default.homedir(), '.sf', 'sessions');
|
|
@@ -318,7 +319,7 @@ function createFooterBar(piTui) {
|
|
|
318
319
|
const tokStr = this.tokens >= 1000 ? `${(this.tokens / 1000).toFixed(1)}k` : `${this.tokens}`;
|
|
319
320
|
const tokens = C.zinc600(`${tokStr} tok`);
|
|
320
321
|
const exchange = this.exchangeOpen === true ? C.emerald('OPEN') : this.exchangeOpen === false ? C.red('CLOSED') : C.zinc600('...');
|
|
321
|
-
const trading = this.tradingEnabled ? C.amber('
|
|
322
|
+
const trading = this.tradingEnabled ? C.amber('trading') : C.zinc600('read-only');
|
|
322
323
|
const help = C.zinc600('/help');
|
|
323
324
|
const leftText = [model, tokens, exchange, trading].join(sep);
|
|
324
325
|
const lw = visibleWidth(leftText);
|
|
@@ -739,6 +740,12 @@ async function agentCommand(thesisId, opts) {
|
|
|
739
740
|
slashCommands.splice(-2, 0, // insert before /clear and /exit
|
|
740
741
|
{ name: 'buy', description: 'TICKER QTY PRICE — quick buy' }, { name: 'sell', description: 'TICKER QTY PRICE — quick sell' }, { name: 'cancel', description: 'ORDER_ID — cancel order' });
|
|
741
742
|
}
|
|
743
|
+
// Load skills and register as slash commands
|
|
744
|
+
const skills = (0, loader_js_1.loadSkills)();
|
|
745
|
+
for (const skill of skills) {
|
|
746
|
+
const trigger = skill.trigger.replace(/^\//, ''); // remove leading /
|
|
747
|
+
slashCommands.splice(-2, 0, { name: trigger, description: `[skill] ${skill.description.slice(0, 50)}` });
|
|
748
|
+
}
|
|
742
749
|
const autocompleteProvider = new CombinedAutocompleteProvider(slashCommands, process.cwd());
|
|
743
750
|
editor.setAutocompleteProvider(autocompleteProvider);
|
|
744
751
|
// Assemble TUI tree
|
|
@@ -1267,16 +1274,34 @@ async function agentCommand(thesisId, opts) {
|
|
|
1267
1274
|
{
|
|
1268
1275
|
name: 'get_orders',
|
|
1269
1276
|
label: 'Orders',
|
|
1270
|
-
description: 'Get current resting orders on Kalshi.',
|
|
1277
|
+
description: 'Get current resting orders on Kalshi. Stale orders (>7 days old AND >10¢ from market) are flagged.',
|
|
1271
1278
|
parameters: Type.Object({
|
|
1272
1279
|
status: Type.Optional(Type.String({ description: 'Filter by status: resting, canceled, executed. Default: resting' })),
|
|
1273
1280
|
}),
|
|
1274
1281
|
execute: async (_toolCallId, params) => {
|
|
1275
|
-
const { getOrders } = await import('../kalshi.js');
|
|
1282
|
+
const { getOrders, getMarketPrice } = await import('../kalshi.js');
|
|
1276
1283
|
const result = await getOrders({ status: params.status || 'resting', limit: 100 });
|
|
1277
1284
|
if (!result)
|
|
1278
1285
|
return { content: [{ type: 'text', text: 'Kalshi not configured.' }], details: {} };
|
|
1279
|
-
|
|
1286
|
+
// Enrich orders with staleness detection
|
|
1287
|
+
const enriched = await Promise.all((result.orders || []).map(async (order) => {
|
|
1288
|
+
const daysSinceCreated = order.created_time
|
|
1289
|
+
? Math.round((Date.now() - new Date(order.created_time).getTime()) / 86400000)
|
|
1290
|
+
: null;
|
|
1291
|
+
let distanceFromMarket = null;
|
|
1292
|
+
let stale = false;
|
|
1293
|
+
try {
|
|
1294
|
+
const price = await getMarketPrice(order.ticker);
|
|
1295
|
+
if (price != null && order.yes_price_dollars) {
|
|
1296
|
+
distanceFromMarket = Math.round(Math.abs(price - parseFloat(order.yes_price_dollars)) * 100);
|
|
1297
|
+
if (daysSinceCreated != null && daysSinceCreated > 7 && distanceFromMarket > 10)
|
|
1298
|
+
stale = true;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
catch { }
|
|
1302
|
+
return { ...order, daysSinceCreated, distanceFromMarket, stale };
|
|
1303
|
+
}));
|
|
1304
|
+
return { content: [{ type: 'text', text: JSON.stringify(enriched, null, 2) }], details: {} };
|
|
1280
1305
|
},
|
|
1281
1306
|
},
|
|
1282
1307
|
{
|
|
@@ -1356,42 +1381,71 @@ async function agentCommand(thesisId, opts) {
|
|
|
1356
1381
|
{
|
|
1357
1382
|
name: 'inspect_book',
|
|
1358
1383
|
label: 'Orderbook',
|
|
1359
|
-
description: 'Get orderbook depth, spread, and liquidity
|
|
1384
|
+
description: 'Get orderbook depth, spread, and liquidity. Returns a status field per market: "ok", "empty_orderbook", "market_closed", or "api_error". Supports multiple tickers in one call — use tickers array for batch position checks.',
|
|
1360
1385
|
parameters: Type.Object({
|
|
1361
|
-
ticker: Type.Optional(Type.String({ description: 'Kalshi market ticker (e.g. KXWTIMAX-26DEC31-T135)' })),
|
|
1386
|
+
ticker: Type.Optional(Type.String({ description: 'Single Kalshi market ticker (e.g. KXWTIMAX-26DEC31-T135)' })),
|
|
1387
|
+
tickers: Type.Optional(Type.Array(Type.String(), { description: 'Multiple Kalshi tickers for batch check (e.g. ["T$135", "T$140", "T$150"])' })),
|
|
1362
1388
|
polyQuery: Type.Optional(Type.String({ description: 'Search Polymarket by keyword (e.g. "oil price above 100")' })),
|
|
1363
1389
|
}),
|
|
1364
1390
|
execute: async (_toolCallId, params) => {
|
|
1365
1391
|
const results = [];
|
|
1366
|
-
|
|
1392
|
+
// Batch: expand tickers array into individual lookups
|
|
1393
|
+
const tickerList = [];
|
|
1394
|
+
if (params.tickers?.length)
|
|
1395
|
+
tickerList.push(...params.tickers);
|
|
1396
|
+
else if (params.ticker)
|
|
1397
|
+
tickerList.push(params.ticker);
|
|
1398
|
+
for (const tkr of tickerList) {
|
|
1367
1399
|
try {
|
|
1368
|
-
const market = await (0, client_js_1.kalshiFetchMarket)(
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
.
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1400
|
+
const market = await (0, client_js_1.kalshiFetchMarket)(tkr);
|
|
1401
|
+
const mStatus = market.status || 'unknown';
|
|
1402
|
+
if (mStatus !== 'open' && mStatus !== 'active') {
|
|
1403
|
+
results.push({
|
|
1404
|
+
venue: 'kalshi', ticker: tkr, title: market.title,
|
|
1405
|
+
status: 'market_closed', reason: `Market status: ${mStatus}. Orderbook unavailable for closed/settled markets.`,
|
|
1406
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
const ob = await (0, kalshi_js_1.getPublicOrderbook)(tkr);
|
|
1411
|
+
const yesBids = (ob?.yes_dollars || [])
|
|
1412
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
1413
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
1414
|
+
const noAsks = (ob?.no_dollars || [])
|
|
1415
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
1416
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
1417
|
+
if (yesBids.length === 0 && noAsks.length === 0) {
|
|
1418
|
+
results.push({
|
|
1419
|
+
venue: 'kalshi', ticker: tkr, title: market.title,
|
|
1420
|
+
status: 'empty_orderbook', reason: 'Market open but no resting orders. Normal for illiquid/OTM contracts. Use lastPrice as reference.',
|
|
1421
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
1422
|
+
volume24h: parseFloat(market.volume_24h_fp || '0'),
|
|
1423
|
+
openInterest: parseFloat(market.open_interest_fp || '0'),
|
|
1424
|
+
expiry: market.close_time || null,
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
else {
|
|
1428
|
+
const bestBid = yesBids[0]?.price || 0;
|
|
1429
|
+
const bestAsk = noAsks.length > 0 ? (100 - noAsks[0].price) : (yesBids[0] ? yesBids[0].price + 1 : 100);
|
|
1430
|
+
const spread = bestAsk - bestBid;
|
|
1431
|
+
const top3Depth = yesBids.slice(0, 3).reduce((s, l) => s + l.size, 0) + noAsks.slice(0, 3).reduce((s, l) => s + l.size, 0);
|
|
1432
|
+
const liq = spread <= 2 && top3Depth >= 500 ? 'high' : spread <= 5 && top3Depth >= 100 ? 'medium' : 'low';
|
|
1433
|
+
results.push({
|
|
1434
|
+
venue: 'kalshi', ticker: tkr, title: market.title, status: 'ok',
|
|
1435
|
+
bestBid, bestAsk, spread, liquidityScore: liq,
|
|
1436
|
+
bidLevels: yesBids.slice(0, 5), askLevels: noAsks.slice(0, 5).map((l) => ({ price: 100 - l.price, size: l.size })),
|
|
1437
|
+
totalBidDepth: yesBids.reduce((s, l) => s + l.size, 0),
|
|
1438
|
+
totalAskDepth: noAsks.reduce((s, l) => s + l.size, 0),
|
|
1439
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
1440
|
+
volume24h: parseFloat(market.volume_24h_fp || '0'),
|
|
1441
|
+
openInterest: parseFloat(market.open_interest_fp || '0'),
|
|
1442
|
+
expiry: market.close_time || null,
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1392
1446
|
}
|
|
1393
1447
|
catch (err) {
|
|
1394
|
-
|
|
1448
|
+
results.push({ venue: 'kalshi', ticker: tkr, status: 'api_error', reason: `Kalshi API error: ${err.message}` });
|
|
1395
1449
|
}
|
|
1396
1450
|
}
|
|
1397
1451
|
if (params.polyQuery) {
|
|
@@ -1512,14 +1566,64 @@ async function agentCommand(thesisId, opts) {
|
|
|
1512
1566
|
{
|
|
1513
1567
|
name: 'get_feed',
|
|
1514
1568
|
label: 'Get Feed',
|
|
1515
|
-
description: 'Get evaluation history
|
|
1569
|
+
description: 'Get evaluation history with topSignal highlighting. The most important signal (largest confidence change or most actionable) is surfaced first so you don\'t have to scan all entries.',
|
|
1516
1570
|
parameters: Type.Object({
|
|
1517
1571
|
hours: Type.Optional(Type.Number({ description: 'Hours of history to fetch (default 24)' })),
|
|
1518
1572
|
}),
|
|
1519
1573
|
execute: async (_toolCallId, params) => {
|
|
1520
1574
|
const result = await sfClient.getFeed(params.hours || 24);
|
|
1575
|
+
const items = Array.isArray(result) ? result : (result?.evaluations || result?.items || []);
|
|
1576
|
+
// Find the most important signal: largest |confidenceDelta|, or newest with actual content
|
|
1577
|
+
let topSignal = null;
|
|
1578
|
+
let topScore = 0;
|
|
1579
|
+
for (const item of items) {
|
|
1580
|
+
let score = 0;
|
|
1581
|
+
const delta = Math.abs(item.confidenceDelta || item.confidence_delta || 0);
|
|
1582
|
+
if (delta > 0)
|
|
1583
|
+
score = delta * 100; // confidence changes are most important
|
|
1584
|
+
else if (item.summary?.length > 50)
|
|
1585
|
+
score = 0.1; // has substance but no delta
|
|
1586
|
+
if (score > topScore) {
|
|
1587
|
+
topScore = score;
|
|
1588
|
+
topSignal = item;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const output = { total: items.length };
|
|
1592
|
+
if (topSignal) {
|
|
1593
|
+
output.topSignal = {
|
|
1594
|
+
summary: topSignal.summary || topSignal.content || '',
|
|
1595
|
+
confidenceDelta: topSignal.confidenceDelta || topSignal.confidence_delta || 0,
|
|
1596
|
+
evaluatedAt: topSignal.evaluatedAt || topSignal.evaluated_at || topSignal.createdAt || '',
|
|
1597
|
+
why: topScore > 1 ? 'Largest confidence movement in this period'
|
|
1598
|
+
: topScore > 0 ? 'Most substantive evaluation (no confidence change)'
|
|
1599
|
+
: 'Most recent evaluation',
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
output.items = items;
|
|
1521
1603
|
return {
|
|
1522
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
1604
|
+
content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
|
|
1605
|
+
details: {},
|
|
1606
|
+
};
|
|
1607
|
+
},
|
|
1608
|
+
},
|
|
1609
|
+
{
|
|
1610
|
+
name: 'get_changes',
|
|
1611
|
+
label: 'Get Changes',
|
|
1612
|
+
description: 'Get recent market changes detected server-side. Returns real price moves (>5¢), new contracts, and removed/settled contracts across Kalshi, Polymarket, and traditional markets. Checked every 15 minutes. Use for situational awareness, discovering new opportunities, or checking if anything material happened recently.',
|
|
1613
|
+
parameters: Type.Object({
|
|
1614
|
+
hours: Type.Optional(Type.Number({ description: 'Hours of history (default 1)' })),
|
|
1615
|
+
}),
|
|
1616
|
+
execute: async (_toolCallId, params) => {
|
|
1617
|
+
const hours = params.hours || 1;
|
|
1618
|
+
const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
|
|
1619
|
+
const apiUrl = process.env.SF_API_URL || 'https://simplefunctions.dev';
|
|
1620
|
+
const res = await fetch(`${apiUrl}/api/changes?since=${encodeURIComponent(since)}&limit=100`);
|
|
1621
|
+
if (!res.ok) {
|
|
1622
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: `API error ${res.status}` }) }], details: {} };
|
|
1623
|
+
}
|
|
1624
|
+
const data = await res.json();
|
|
1625
|
+
return {
|
|
1626
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
1523
1627
|
details: {},
|
|
1524
1628
|
};
|
|
1525
1629
|
},
|
|
@@ -1537,7 +1641,13 @@ async function agentCommand(thesisId, opts) {
|
|
|
1537
1641
|
}), { description: 'Node probability overrides' }),
|
|
1538
1642
|
}),
|
|
1539
1643
|
execute: async (_toolCallId, params) => {
|
|
1540
|
-
//
|
|
1644
|
+
// Refresh context before simulation to avoid stale confidence values
|
|
1645
|
+
if (resolvedThesisId) {
|
|
1646
|
+
try {
|
|
1647
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
1648
|
+
}
|
|
1649
|
+
catch { }
|
|
1650
|
+
}
|
|
1541
1651
|
const ctx = latestContext;
|
|
1542
1652
|
const allNodes = [];
|
|
1543
1653
|
function flatten(nodes) {
|
|
@@ -1551,10 +1661,28 @@ async function agentCommand(thesisId, opts) {
|
|
|
1551
1661
|
flatten(rawNodes);
|
|
1552
1662
|
const treeNodes = rawNodes.filter((n) => n.depth === 0 || (n.depth === undefined && !n.id.includes('.')));
|
|
1553
1663
|
const overrideMap = new Map(params.overrides.map((o) => [o.nodeId, o.newProbability]));
|
|
1664
|
+
// Propagate child node overrides to parent nodes.
|
|
1665
|
+
// If n2.2 is overridden, recalculate n2's effective probability
|
|
1666
|
+
// as the average of its children's (possibly overridden) probabilities.
|
|
1667
|
+
function effectiveProb(node) {
|
|
1668
|
+
// Direct override on this node
|
|
1669
|
+
if (overrideMap.has(node.id))
|
|
1670
|
+
return overrideMap.get(node.id);
|
|
1671
|
+
// If node has children, aggregate from children
|
|
1672
|
+
if (node.children?.length > 0) {
|
|
1673
|
+
const childProbs = node.children.map((c) => effectiveProb(c));
|
|
1674
|
+
const childImps = node.children.map((c) => c.importance || 1);
|
|
1675
|
+
const totalImp = childImps.reduce((s, w) => s + w, 0);
|
|
1676
|
+
if (totalImp > 0) {
|
|
1677
|
+
return childProbs.reduce((s, p, i) => s + p * childImps[i], 0) / totalImp;
|
|
1678
|
+
}
|
|
1679
|
+
return childProbs.reduce((s, p) => s + p, 0) / childProbs.length;
|
|
1680
|
+
}
|
|
1681
|
+
return node.probability ?? 0;
|
|
1682
|
+
}
|
|
1554
1683
|
const oldConf = treeNodes.reduce((s, n) => s + (n.probability || 0) * (n.importance || 0), 0);
|
|
1555
1684
|
const newConf = treeNodes.reduce((s, n) => {
|
|
1556
|
-
|
|
1557
|
-
return s + p * (n.importance || 0);
|
|
1685
|
+
return s + effectiveProb(n) * (n.importance || 0);
|
|
1558
1686
|
}, 0);
|
|
1559
1687
|
const nodeScales = new Map();
|
|
1560
1688
|
for (const [nid, np] of overrideMap.entries()) {
|
|
@@ -1589,12 +1717,20 @@ async function agentCommand(thesisId, opts) {
|
|
|
1589
1717
|
signal: Math.abs(newEdge - oldEdge) < 1 ? 'unchanged' : (oldEdge > 0 && newEdge < 0) || (oldEdge < 0 && newEdge > 0) ? 'REVERSED' : Math.abs(newEdge) < 2 ? 'GONE' : 'reduced',
|
|
1590
1718
|
};
|
|
1591
1719
|
}).filter((e) => e.signal !== 'unchanged');
|
|
1720
|
+
// Server confidence = LLM's holistic assessment (includes factors beyond the tree)
|
|
1721
|
+
// Tree confidence = weighted sum of node probabilities (pure math from causal tree)
|
|
1722
|
+
// These measure different things and will often differ.
|
|
1723
|
+
const serverConf = ctx.confidence != null ? Math.round(Number(ctx.confidence) * 100) : null;
|
|
1592
1724
|
const result = {
|
|
1593
1725
|
overrides: params.overrides.map((o) => {
|
|
1594
1726
|
const node = allNodes.find((n) => n.id === o.nodeId);
|
|
1595
1727
|
return { nodeId: o.nodeId, label: node?.label || o.nodeId, oldProb: node?.probability, newProb: o.newProbability };
|
|
1596
1728
|
}),
|
|
1597
|
-
|
|
1729
|
+
serverConfidence: serverConf,
|
|
1730
|
+
treeConfidence: { old: Math.round(oldConf * 100), new: Math.round(newConf * 100), delta: Math.round((newConf - oldConf) * 100) },
|
|
1731
|
+
note: serverConf != null && Math.abs(serverConf - Math.round(oldConf * 100)) > 5
|
|
1732
|
+
? `serverConfidence (${serverConf}%) differs from treeConfidence (${Math.round(oldConf * 100)}%) because the LLM evaluation considers factors beyond the causal tree.`
|
|
1733
|
+
: undefined,
|
|
1598
1734
|
affectedEdges: edges,
|
|
1599
1735
|
};
|
|
1600
1736
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], details: {} };
|
|
@@ -2071,7 +2207,7 @@ ${topEdges}
|
|
|
2071
2207
|
}
|
|
2072
2208
|
}
|
|
2073
2209
|
if (event.type === 'tool_execution_start') {
|
|
2074
|
-
const toolLine = new MutableLine(C.zinc600(` \
|
|
2210
|
+
const toolLine = new MutableLine(C.zinc600(` \u25B8 ${event.toolName}...`));
|
|
2075
2211
|
toolStartTimes.set(event.toolCallId || event.toolName, Date.now());
|
|
2076
2212
|
toolLines.set(event.toolCallId || event.toolName, toolLine);
|
|
2077
2213
|
chatContainer.addChild(toolLine);
|
|
@@ -2090,7 +2226,7 @@ ${topEdges}
|
|
|
2090
2226
|
line.setText(C.red(` \u2717 ${event.toolName} (${elapsed}s) error`));
|
|
2091
2227
|
}
|
|
2092
2228
|
else {
|
|
2093
|
-
line.setText(C.zinc600(` \
|
|
2229
|
+
line.setText(C.zinc600(` \u25B8 ${event.toolName} `) + C.emerald(`\u2713`) + C.zinc600(` ${elapsed}s`));
|
|
2094
2230
|
}
|
|
2095
2231
|
}
|
|
2096
2232
|
toolStartTimes.delete(key);
|
|
@@ -2121,6 +2257,10 @@ ${topEdges}
|
|
|
2121
2257
|
C.emerald('/sell ') + C.zinc400('TICKER QTY PRICE \u2014 quick sell') + '\n' +
|
|
2122
2258
|
C.emerald('/cancel ') + C.zinc400('ORDER_ID \u2014 cancel order') + '\n' +
|
|
2123
2259
|
C.zinc600('\u2500'.repeat(30)) + '\n') : '') +
|
|
2260
|
+
(skills.length > 0 ? (C.zinc600('\u2500'.repeat(30)) + '\n' +
|
|
2261
|
+
C.zinc200(bold('Skills')) + '\n' +
|
|
2262
|
+
skills.map(s => C.emerald(`/${s.name.padEnd(10)}`) + C.zinc400(s.description.slice(0, 45))).join('\n') + '\n' +
|
|
2263
|
+
C.zinc600('\u2500'.repeat(30)) + '\n') : '') +
|
|
2124
2264
|
C.emerald('/clear ') + C.zinc400('Clear screen (keeps history)') + '\n' +
|
|
2125
2265
|
C.emerald('/exit ') + C.zinc400('Exit (auto-saves)'));
|
|
2126
2266
|
addSpacer();
|
|
@@ -2603,8 +2743,29 @@ Output a structured summary. Be concise but preserve every important detail —
|
|
|
2603
2743
|
cleanup();
|
|
2604
2744
|
return true;
|
|
2605
2745
|
}
|
|
2606
|
-
default:
|
|
2746
|
+
default: {
|
|
2747
|
+
// Check if it's a skill trigger
|
|
2748
|
+
const skill = skills.find(s => s.trigger === command);
|
|
2749
|
+
if (skill) {
|
|
2750
|
+
addSpacer();
|
|
2751
|
+
addSystemText(C.zinc200(`Running skill: ${bold(skill.name)}`) + C.zinc600(` \u2014 ${skill.description.slice(0, 60)}`));
|
|
2752
|
+
addSpacer();
|
|
2753
|
+
tui.requestRender();
|
|
2754
|
+
// Inject the skill prompt → agent executes using existing tools
|
|
2755
|
+
isProcessing = true;
|
|
2756
|
+
try {
|
|
2757
|
+
await agent.prompt(skill.prompt);
|
|
2758
|
+
}
|
|
2759
|
+
catch (err) {
|
|
2760
|
+
addSystemText(C.red(`Skill error: ${err.message}`));
|
|
2761
|
+
}
|
|
2762
|
+
finally {
|
|
2763
|
+
isProcessing = false;
|
|
2764
|
+
}
|
|
2765
|
+
return true;
|
|
2766
|
+
}
|
|
2607
2767
|
return false;
|
|
2768
|
+
}
|
|
2608
2769
|
}
|
|
2609
2770
|
}
|
|
2610
2771
|
// ── Editor submit handler ──────────────────────────────────────────────────
|
|
@@ -3184,42 +3345,71 @@ async function runPlainTextAgent(params) {
|
|
|
3184
3345
|
{
|
|
3185
3346
|
name: 'inspect_book',
|
|
3186
3347
|
label: 'Orderbook',
|
|
3187
|
-
description: 'Get orderbook depth, spread, and liquidity
|
|
3348
|
+
description: 'Get orderbook depth, spread, and liquidity. Returns a status field per market: "ok", "empty_orderbook", "market_closed", or "api_error". Supports multiple tickers in one call — use tickers array for batch position checks.',
|
|
3188
3349
|
parameters: Type.Object({
|
|
3189
|
-
ticker: Type.Optional(Type.String({ description: 'Kalshi market ticker (e.g. KXWTIMAX-26DEC31-T135)' })),
|
|
3350
|
+
ticker: Type.Optional(Type.String({ description: 'Single Kalshi market ticker (e.g. KXWTIMAX-26DEC31-T135)' })),
|
|
3351
|
+
tickers: Type.Optional(Type.Array(Type.String(), { description: 'Multiple Kalshi tickers for batch check (e.g. ["T$135", "T$140", "T$150"])' })),
|
|
3190
3352
|
polyQuery: Type.Optional(Type.String({ description: 'Search Polymarket by keyword (e.g. "oil price above 100")' })),
|
|
3191
3353
|
}),
|
|
3192
3354
|
execute: async (_toolCallId, params) => {
|
|
3193
3355
|
const results = [];
|
|
3194
|
-
|
|
3356
|
+
// Batch: expand tickers array into individual lookups
|
|
3357
|
+
const tickerList = [];
|
|
3358
|
+
if (params.tickers?.length)
|
|
3359
|
+
tickerList.push(...params.tickers);
|
|
3360
|
+
else if (params.ticker)
|
|
3361
|
+
tickerList.push(params.ticker);
|
|
3362
|
+
for (const tkr of tickerList) {
|
|
3195
3363
|
try {
|
|
3196
|
-
const market = await (0, client_js_1.kalshiFetchMarket)(
|
|
3197
|
-
const
|
|
3198
|
-
|
|
3199
|
-
.
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3364
|
+
const market = await (0, client_js_1.kalshiFetchMarket)(tkr);
|
|
3365
|
+
const mStatus = market.status || 'unknown';
|
|
3366
|
+
if (mStatus !== 'open' && mStatus !== 'active') {
|
|
3367
|
+
results.push({
|
|
3368
|
+
venue: 'kalshi', ticker: tkr, title: market.title,
|
|
3369
|
+
status: 'market_closed', reason: `Market status: ${mStatus}. Orderbook unavailable for closed/settled markets.`,
|
|
3370
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
const ob = await (0, kalshi_js_1.getPublicOrderbook)(tkr);
|
|
3375
|
+
const yesBids = (ob?.yes_dollars || [])
|
|
3376
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
3377
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
3378
|
+
const noAsks = (ob?.no_dollars || [])
|
|
3379
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
3380
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
3381
|
+
if (yesBids.length === 0 && noAsks.length === 0) {
|
|
3382
|
+
results.push({
|
|
3383
|
+
venue: 'kalshi', ticker: tkr, title: market.title,
|
|
3384
|
+
status: 'empty_orderbook', reason: 'Market open but no resting orders. Normal for illiquid/OTM contracts. Use lastPrice as reference.',
|
|
3385
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
3386
|
+
volume24h: parseFloat(market.volume_24h_fp || '0'),
|
|
3387
|
+
openInterest: parseFloat(market.open_interest_fp || '0'),
|
|
3388
|
+
expiry: market.close_time || null,
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
else {
|
|
3392
|
+
const bestBid = yesBids[0]?.price || 0;
|
|
3393
|
+
const bestAsk = noAsks.length > 0 ? (100 - noAsks[0].price) : (yesBids[0] ? yesBids[0].price + 1 : 100);
|
|
3394
|
+
const spread = bestAsk - bestBid;
|
|
3395
|
+
const top3Depth = yesBids.slice(0, 3).reduce((s, l) => s + l.size, 0) + noAsks.slice(0, 3).reduce((s, l) => s + l.size, 0);
|
|
3396
|
+
const liq = spread <= 2 && top3Depth >= 500 ? 'high' : spread <= 5 && top3Depth >= 100 ? 'medium' : 'low';
|
|
3397
|
+
results.push({
|
|
3398
|
+
venue: 'kalshi', ticker: tkr, title: market.title, status: 'ok',
|
|
3399
|
+
bestBid, bestAsk, spread, liquidityScore: liq,
|
|
3400
|
+
bidLevels: yesBids.slice(0, 5), askLevels: noAsks.slice(0, 5).map((l) => ({ price: 100 - l.price, size: l.size })),
|
|
3401
|
+
totalBidDepth: yesBids.reduce((s, l) => s + l.size, 0),
|
|
3402
|
+
totalAskDepth: noAsks.reduce((s, l) => s + l.size, 0),
|
|
3403
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
3404
|
+
volume24h: parseFloat(market.volume_24h_fp || '0'),
|
|
3405
|
+
openInterest: parseFloat(market.open_interest_fp || '0'),
|
|
3406
|
+
expiry: market.close_time || null,
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3220
3410
|
}
|
|
3221
3411
|
catch (err) {
|
|
3222
|
-
|
|
3412
|
+
results.push({ venue: 'kalshi', ticker: tkr, status: 'api_error', reason: `Kalshi API error: ${err.message}` });
|
|
3223
3413
|
}
|
|
3224
3414
|
}
|
|
3225
3415
|
if (params.polyQuery) {
|
|
@@ -3327,18 +3517,61 @@ async function runPlainTextAgent(params) {
|
|
|
3327
3517
|
{
|
|
3328
3518
|
name: 'get_feed',
|
|
3329
3519
|
label: 'Get Feed',
|
|
3330
|
-
description: 'Get evaluation history
|
|
3520
|
+
description: 'Get evaluation history with topSignal highlighting. The most important signal is surfaced first.',
|
|
3331
3521
|
parameters: Type.Object({
|
|
3332
3522
|
hours: Type.Optional(Type.Number({ description: 'Hours of history to fetch (default 24)' })),
|
|
3333
3523
|
}),
|
|
3334
3524
|
execute: async (_id, p) => {
|
|
3335
3525
|
const result = await sfClient.getFeed(p.hours || 24);
|
|
3526
|
+
const items = Array.isArray(result) ? result : (result?.evaluations || result?.items || []);
|
|
3527
|
+
let topSignal = null;
|
|
3528
|
+
let topScore = 0;
|
|
3529
|
+
for (const item of items) {
|
|
3530
|
+
let score = 0;
|
|
3531
|
+
const delta = Math.abs(item.confidenceDelta || item.confidence_delta || 0);
|
|
3532
|
+
if (delta > 0)
|
|
3533
|
+
score = delta * 100;
|
|
3534
|
+
else if (item.summary?.length > 50)
|
|
3535
|
+
score = 0.1;
|
|
3536
|
+
if (score > topScore) {
|
|
3537
|
+
topScore = score;
|
|
3538
|
+
topSignal = item;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
const output = { total: items.length };
|
|
3542
|
+
if (topSignal) {
|
|
3543
|
+
output.topSignal = {
|
|
3544
|
+
summary: topSignal.summary || topSignal.content || '',
|
|
3545
|
+
confidenceDelta: topSignal.confidenceDelta || topSignal.confidence_delta || 0,
|
|
3546
|
+
evaluatedAt: topSignal.evaluatedAt || topSignal.evaluated_at || '',
|
|
3547
|
+
why: topScore > 1 ? 'Largest confidence movement' : topScore > 0 ? 'Most substantive (no confidence change)' : 'Most recent',
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
output.items = items;
|
|
3336
3551
|
return {
|
|
3337
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
3552
|
+
content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
|
|
3338
3553
|
details: {},
|
|
3339
3554
|
};
|
|
3340
3555
|
},
|
|
3341
3556
|
},
|
|
3557
|
+
{
|
|
3558
|
+
name: 'get_changes',
|
|
3559
|
+
label: 'Get Changes',
|
|
3560
|
+
description: 'Get recent market changes detected server-side. Returns real price moves (>5¢), new contracts, and removed/settled contracts across Kalshi, Polymarket, and traditional markets. Use for situational awareness and discovering new opportunities.',
|
|
3561
|
+
parameters: Type.Object({
|
|
3562
|
+
hours: Type.Optional(Type.Number({ description: 'Hours of history (default 1)' })),
|
|
3563
|
+
}),
|
|
3564
|
+
execute: async (_id, p) => {
|
|
3565
|
+
const hours = p.hours || 1;
|
|
3566
|
+
const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
|
|
3567
|
+
const apiUrl = process.env.SF_API_URL || 'https://simplefunctions.dev';
|
|
3568
|
+
const res = await fetch(`${apiUrl}/api/changes?since=${encodeURIComponent(since)}&limit=100`);
|
|
3569
|
+
if (!res.ok)
|
|
3570
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: `API error ${res.status}` }) }], details: {} };
|
|
3571
|
+
const data = await res.json();
|
|
3572
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
|
|
3573
|
+
},
|
|
3574
|
+
},
|
|
3342
3575
|
{
|
|
3343
3576
|
name: 'explore_public',
|
|
3344
3577
|
label: 'Explore Public Theses',
|
|
@@ -3783,7 +4016,7 @@ ${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice
|
|
|
3783
4016
|
}
|
|
3784
4017
|
}
|
|
3785
4018
|
if (event.type === 'tool_execution_start') {
|
|
3786
|
-
process.stderr.write(` \
|
|
4019
|
+
process.stderr.write(` \u25B8 ${event.toolName}...\n`);
|
|
3787
4020
|
}
|
|
3788
4021
|
if (event.type === 'tool_execution_end') {
|
|
3789
4022
|
const status = event.isError ? '\u2717' : '\u2713';
|
package/dist/commands/balance.js
CHANGED
|
@@ -6,7 +6,7 @@ const utils_js_1 = require("../utils.js");
|
|
|
6
6
|
async function balanceCommand(opts) {
|
|
7
7
|
const result = await (0, kalshi_js_1.getBalance)();
|
|
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, null, 2));
|
|
12
12
|
return;
|
package/dist/commands/book.js
CHANGED
|
@@ -152,7 +152,7 @@ async function bookCommand(tickers, opts) {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
if (results.length === 0) {
|
|
155
|
-
console.log(
|
|
155
|
+
console.log(`\n ${utils_js_1.c.dim}No markets found. Check the ticker and try again.${utils_js_1.c.reset}\n`);
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
158
|
// ── JSON output ──
|
package/dist/commands/context.js
CHANGED
|
@@ -4,6 +4,7 @@ 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 share_js_1 = require("../share.js");
|
|
7
8
|
const SF_API_URL = process.env.SF_API_URL || 'https://simplefunctions.dev';
|
|
8
9
|
async function contextCommand(id, opts) {
|
|
9
10
|
// ── Mode 1: No thesis ID → global market intelligence ─────────────────────
|
|
@@ -14,6 +15,10 @@ async function contextCommand(id, opts) {
|
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
const data = await res.json();
|
|
18
|
+
if (opts.share) {
|
|
19
|
+
await (0, share_js_1.shareOutput)('context', '', data);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
17
22
|
if (opts.json) {
|
|
18
23
|
console.log(JSON.stringify(data, null, 2));
|
|
19
24
|
return;
|