@spfunctions/cli 1.1.9 → 1.3.0
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 +375 -6
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +138 -120
- package/dist/commands/trade.js +2 -3
- package/dist/index.js +4 -3
- package/dist/kalshi.d.ts +1 -1
- package/package.json +2 -2
package/dist/commands/agent.js
CHANGED
|
@@ -1007,6 +1007,73 @@ async function agentCommand(thesisId, opts) {
|
|
|
1007
1007
|
}
|
|
1008
1008
|
},
|
|
1009
1009
|
},
|
|
1010
|
+
{
|
|
1011
|
+
name: 'create_thesis',
|
|
1012
|
+
label: 'Create Thesis',
|
|
1013
|
+
description: 'Create a new thesis from a raw thesis statement. Returns the thesis ID, confidence, node count, and edge count.',
|
|
1014
|
+
parameters: Type.Object({
|
|
1015
|
+
rawThesis: Type.String({ description: 'The raw thesis statement to create' }),
|
|
1016
|
+
webhookUrl: Type.Optional(Type.String({ description: 'Optional webhook URL for notifications' })),
|
|
1017
|
+
}),
|
|
1018
|
+
execute: async (_toolCallId, params) => {
|
|
1019
|
+
const result = await sfClient.createThesis(params.rawThesis, true);
|
|
1020
|
+
const thesis = result.thesis || result;
|
|
1021
|
+
const nodeCount = thesis.causalTree?.nodes?.length || 0;
|
|
1022
|
+
const edgeCount = (thesis.edges || []).length;
|
|
1023
|
+
const confidence = typeof thesis.confidence === 'number' ? Math.round(thesis.confidence * 100) : 0;
|
|
1024
|
+
return {
|
|
1025
|
+
content: [{ type: 'text', text: `Thesis created.\nID: ${thesis.id}\nConfidence: ${confidence}%\nNodes: ${nodeCount}\nEdges: ${edgeCount}` }],
|
|
1026
|
+
details: {},
|
|
1027
|
+
};
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
name: 'get_edges',
|
|
1032
|
+
label: 'Get Edges',
|
|
1033
|
+
description: 'Get top edges across all active theses. Returns the top 10 edges sorted by absolute edge size with ticker, market name, edge size, direction, and venue.',
|
|
1034
|
+
parameters: emptyParams,
|
|
1035
|
+
execute: async () => {
|
|
1036
|
+
const { theses } = await sfClient.listTheses();
|
|
1037
|
+
const activeTheses = (theses || []).filter((t) => t.status === 'active' || t.status === 'monitoring');
|
|
1038
|
+
const allEdges = [];
|
|
1039
|
+
for (const t of activeTheses) {
|
|
1040
|
+
try {
|
|
1041
|
+
const ctx = await sfClient.getContext(t.id);
|
|
1042
|
+
for (const edge of (ctx.edges || [])) {
|
|
1043
|
+
allEdges.push({ ...edge, thesisId: t.id });
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
catch { /* skip inaccessible theses */ }
|
|
1047
|
+
}
|
|
1048
|
+
allEdges.sort((a, b) => Math.abs(b.edge || b.edgeSize || 0) - Math.abs(a.edge || a.edgeSize || 0));
|
|
1049
|
+
const top10 = allEdges.slice(0, 10).map((e) => ({
|
|
1050
|
+
ticker: e.marketId || e.ticker || '-',
|
|
1051
|
+
market: e.market || e.marketTitle || '-',
|
|
1052
|
+
edge: e.edge || e.edgeSize || 0,
|
|
1053
|
+
direction: e.direction || 'yes',
|
|
1054
|
+
venue: e.venue || 'kalshi',
|
|
1055
|
+
}));
|
|
1056
|
+
return {
|
|
1057
|
+
content: [{ type: 'text', text: JSON.stringify(top10, null, 2) }],
|
|
1058
|
+
details: {},
|
|
1059
|
+
};
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
name: 'get_feed',
|
|
1064
|
+
label: 'Get Feed',
|
|
1065
|
+
description: 'Get evaluation history / activity feed. Shows recent evaluations, signals, and changes across theses.',
|
|
1066
|
+
parameters: Type.Object({
|
|
1067
|
+
hours: Type.Optional(Type.Number({ description: 'Hours of history to fetch (default 24)' })),
|
|
1068
|
+
}),
|
|
1069
|
+
execute: async (_toolCallId, params) => {
|
|
1070
|
+
const result = await sfClient.getFeed(params.hours || 24);
|
|
1071
|
+
return {
|
|
1072
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1073
|
+
details: {},
|
|
1074
|
+
};
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1010
1077
|
];
|
|
1011
1078
|
// ── What-if tool (always available) ────────────────────────────────────────
|
|
1012
1079
|
tools.push({
|
|
@@ -1100,8 +1167,8 @@ async function agentCommand(thesisId, opts) {
|
|
|
1100
1167
|
}),
|
|
1101
1168
|
execute: async (_toolCallId, params) => {
|
|
1102
1169
|
const { createOrder } = await import('../kalshi.js');
|
|
1103
|
-
const
|
|
1104
|
-
const maxCost = ((
|
|
1170
|
+
const priceCents = params.price_cents ? Math.round(Number(params.price_cents)) : undefined;
|
|
1171
|
+
const maxCost = ((priceCents || 99) * params.count / 100).toFixed(2);
|
|
1105
1172
|
// Show preview
|
|
1106
1173
|
const preview = [
|
|
1107
1174
|
C.zinc200(bold('ORDER PREVIEW')),
|
|
@@ -1110,7 +1177,7 @@ async function agentCommand(thesisId, opts) {
|
|
|
1110
1177
|
` Action: ${params.action.toUpperCase()}`,
|
|
1111
1178
|
` Quantity: ${params.count}`,
|
|
1112
1179
|
` Type: ${params.type}`,
|
|
1113
|
-
|
|
1180
|
+
priceCents ? ` Price: ${priceCents}\u00A2` : '',
|
|
1114
1181
|
` Max cost: $${maxCost}`,
|
|
1115
1182
|
].filter(Boolean).join('\n');
|
|
1116
1183
|
addSystemText(preview);
|
|
@@ -1128,7 +1195,7 @@ async function agentCommand(thesisId, opts) {
|
|
|
1128
1195
|
action: params.action,
|
|
1129
1196
|
type: params.type,
|
|
1130
1197
|
count: params.count,
|
|
1131
|
-
...(
|
|
1198
|
+
...(priceCents ? { yes_price: priceCents } : {}),
|
|
1132
1199
|
});
|
|
1133
1200
|
const order = result.order || result;
|
|
1134
1201
|
return {
|
|
@@ -1217,6 +1284,10 @@ When the conversation produces a concrete trade idea (specific contract, directi
|
|
|
1217
1284
|
- After creating, confirm the strategy details and mention that sf runtime --dangerous can execute it.
|
|
1218
1285
|
- If the user says "change the stop loss on T150 to 30", use update_strategy.
|
|
1219
1286
|
|
|
1287
|
+
## Trading status
|
|
1288
|
+
|
|
1289
|
+
${config.tradingEnabled ? 'Trading is ENABLED. You have place_order and cancel_order tools.' : 'Trading is DISABLED. You cannot place or cancel orders. Tell the user to run `sf setup --enable-trading` to unlock trading.'}
|
|
1290
|
+
|
|
1220
1291
|
## Current thesis state
|
|
1221
1292
|
|
|
1222
1293
|
Thesis: ${ctx.thesis || ctx.rawThesis || 'N/A'}
|
|
@@ -1762,7 +1833,7 @@ Output a structured summary. Be concise but preserve every important detail —
|
|
|
1762
1833
|
const result = await createOrder({
|
|
1763
1834
|
ticker, side: 'yes', action: 'buy', type: 'limit',
|
|
1764
1835
|
count: parseInt(qtyStr),
|
|
1765
|
-
yes_price:
|
|
1836
|
+
yes_price: parseInt(priceStr),
|
|
1766
1837
|
});
|
|
1767
1838
|
addSystemText(C.emerald('\u2713 Order placed: ' + ((result.order || result).order_id || 'OK')));
|
|
1768
1839
|
}
|
|
@@ -1794,7 +1865,7 @@ Output a structured summary. Be concise but preserve every important detail —
|
|
|
1794
1865
|
const result = await createOrder({
|
|
1795
1866
|
ticker, side: 'yes', action: 'sell', type: 'limit',
|
|
1796
1867
|
count: parseInt(qtyStr),
|
|
1797
|
-
yes_price:
|
|
1868
|
+
yes_price: parseInt(priceStr),
|
|
1798
1869
|
});
|
|
1799
1870
|
addSystemText(C.emerald('\u2713 Order placed: ' + ((result.order || result).order_id || 'OK')));
|
|
1800
1871
|
}
|
|
@@ -2299,7 +2370,305 @@ async function runPlainTextAgent(params) {
|
|
|
2299
2370
|
}
|
|
2300
2371
|
},
|
|
2301
2372
|
},
|
|
2373
|
+
{
|
|
2374
|
+
name: 'create_thesis',
|
|
2375
|
+
label: 'Create Thesis',
|
|
2376
|
+
description: 'Create a new thesis from a raw thesis statement. Returns the thesis ID, confidence, node count, and edge count.',
|
|
2377
|
+
parameters: Type.Object({
|
|
2378
|
+
rawThesis: Type.String({ description: 'The raw thesis statement to create' }),
|
|
2379
|
+
webhookUrl: Type.Optional(Type.String({ description: 'Optional webhook URL for notifications' })),
|
|
2380
|
+
}),
|
|
2381
|
+
execute: async (_id, p) => {
|
|
2382
|
+
const result = await sfClient.createThesis(p.rawThesis, true);
|
|
2383
|
+
const thesis = result.thesis || result;
|
|
2384
|
+
const nodeCount = thesis.causalTree?.nodes?.length || 0;
|
|
2385
|
+
const edgeCount = (thesis.edges || []).length;
|
|
2386
|
+
const confidence = typeof thesis.confidence === 'number' ? Math.round(thesis.confidence * 100) : 0;
|
|
2387
|
+
return {
|
|
2388
|
+
content: [{ type: 'text', text: `Thesis created.\nID: ${thesis.id}\nConfidence: ${confidence}%\nNodes: ${nodeCount}\nEdges: ${edgeCount}` }],
|
|
2389
|
+
details: {},
|
|
2390
|
+
};
|
|
2391
|
+
},
|
|
2392
|
+
},
|
|
2393
|
+
{
|
|
2394
|
+
name: 'get_edges',
|
|
2395
|
+
label: 'Get Edges',
|
|
2396
|
+
description: 'Get top edges across all active theses. Returns the top 10 edges sorted by absolute edge size with ticker, market name, edge size, direction, and venue.',
|
|
2397
|
+
parameters: emptyParams,
|
|
2398
|
+
execute: async () => {
|
|
2399
|
+
const { theses } = await sfClient.listTheses();
|
|
2400
|
+
const activeTheses = (theses || []).filter((t) => t.status === 'active' || t.status === 'monitoring');
|
|
2401
|
+
const allEdges = [];
|
|
2402
|
+
for (const t of activeTheses) {
|
|
2403
|
+
try {
|
|
2404
|
+
const ctx = await sfClient.getContext(t.id);
|
|
2405
|
+
for (const edge of (ctx.edges || [])) {
|
|
2406
|
+
allEdges.push({ ...edge, thesisId: t.id });
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
catch { /* skip inaccessible theses */ }
|
|
2410
|
+
}
|
|
2411
|
+
allEdges.sort((a, b) => Math.abs(b.edge || b.edgeSize || 0) - Math.abs(a.edge || a.edgeSize || 0));
|
|
2412
|
+
const top10 = allEdges.slice(0, 10).map((e) => ({
|
|
2413
|
+
ticker: e.marketId || e.ticker || '-',
|
|
2414
|
+
market: e.market || e.marketTitle || '-',
|
|
2415
|
+
edge: e.edge || e.edgeSize || 0,
|
|
2416
|
+
direction: e.direction || 'yes',
|
|
2417
|
+
venue: e.venue || 'kalshi',
|
|
2418
|
+
}));
|
|
2419
|
+
return {
|
|
2420
|
+
content: [{ type: 'text', text: JSON.stringify(top10, null, 2) }],
|
|
2421
|
+
details: {},
|
|
2422
|
+
};
|
|
2423
|
+
},
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
name: 'get_feed',
|
|
2427
|
+
label: 'Get Feed',
|
|
2428
|
+
description: 'Get evaluation history / activity feed. Shows recent evaluations, signals, and changes across theses.',
|
|
2429
|
+
parameters: Type.Object({
|
|
2430
|
+
hours: Type.Optional(Type.Number({ description: 'Hours of history to fetch (default 24)' })),
|
|
2431
|
+
}),
|
|
2432
|
+
execute: async (_id, p) => {
|
|
2433
|
+
const result = await sfClient.getFeed(p.hours || 24);
|
|
2434
|
+
return {
|
|
2435
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
2436
|
+
details: {},
|
|
2437
|
+
};
|
|
2438
|
+
},
|
|
2439
|
+
},
|
|
2440
|
+
{
|
|
2441
|
+
name: 'explore_public',
|
|
2442
|
+
label: 'Explore Public Theses',
|
|
2443
|
+
description: 'Browse public theses from other users. No auth required. Pass a slug to get details, or omit to list all.',
|
|
2444
|
+
parameters: Type.Object({
|
|
2445
|
+
slug: Type.Optional(Type.String({ description: 'Specific thesis slug, or empty to list all' })),
|
|
2446
|
+
}),
|
|
2447
|
+
execute: async (_id, p) => {
|
|
2448
|
+
const base = 'https://simplefunctions.dev';
|
|
2449
|
+
if (p.slug) {
|
|
2450
|
+
const res = await fetch(`${base}/api/public/thesis/${p.slug}`);
|
|
2451
|
+
if (!res.ok)
|
|
2452
|
+
return { content: [{ type: 'text', text: `Not found: ${p.slug}` }], details: {} };
|
|
2453
|
+
const data = await res.json();
|
|
2454
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
|
|
2455
|
+
}
|
|
2456
|
+
const res = await fetch(`${base}/api/public/theses`);
|
|
2457
|
+
if (!res.ok)
|
|
2458
|
+
return { content: [{ type: 'text', text: 'Failed to fetch public theses' }], details: {} };
|
|
2459
|
+
const data = await res.json();
|
|
2460
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
|
|
2461
|
+
},
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
name: 'create_strategy',
|
|
2465
|
+
label: 'Create Strategy',
|
|
2466
|
+
description: 'Create a trading strategy for a thesis. Extract hard conditions (entryBelow/stopLoss/takeProfit as cents) and soft conditions from conversation. Called when user mentions specific trade ideas.',
|
|
2467
|
+
parameters: Type.Object({
|
|
2468
|
+
thesisId: Type.String({ description: 'Thesis ID' }),
|
|
2469
|
+
marketId: Type.String({ description: 'Market ticker e.g. KXWTIMAX-26DEC31-T150' }),
|
|
2470
|
+
market: Type.String({ description: 'Human-readable market name' }),
|
|
2471
|
+
direction: Type.String({ description: 'yes or no' }),
|
|
2472
|
+
horizon: Type.Optional(Type.String({ description: 'short, medium, or long. Default: medium' })),
|
|
2473
|
+
entryBelow: Type.Optional(Type.Number({ description: 'Entry trigger: ask <= this value (cents)' })),
|
|
2474
|
+
entryAbove: Type.Optional(Type.Number({ description: 'Entry trigger: ask >= this value (cents, for NO direction)' })),
|
|
2475
|
+
stopLoss: Type.Optional(Type.Number({ description: 'Stop loss: bid <= this value (cents)' })),
|
|
2476
|
+
takeProfit: Type.Optional(Type.Number({ description: 'Take profit: bid >= this value (cents)' })),
|
|
2477
|
+
maxQuantity: Type.Optional(Type.Number({ description: 'Max total contracts. Default: 500' })),
|
|
2478
|
+
perOrderQuantity: Type.Optional(Type.Number({ description: 'Contracts per order. Default: 50' })),
|
|
2479
|
+
softConditions: Type.Optional(Type.String({ description: 'LLM-evaluated conditions e.g. "only enter when n3 > 60%"' })),
|
|
2480
|
+
rationale: Type.Optional(Type.String({ description: 'Full logic description' })),
|
|
2481
|
+
}),
|
|
2482
|
+
execute: async (_id, p) => {
|
|
2483
|
+
const result = await sfClient.createStrategyAPI(p.thesisId, {
|
|
2484
|
+
marketId: p.marketId,
|
|
2485
|
+
market: p.market,
|
|
2486
|
+
direction: p.direction,
|
|
2487
|
+
horizon: p.horizon,
|
|
2488
|
+
entryBelow: p.entryBelow,
|
|
2489
|
+
entryAbove: p.entryAbove,
|
|
2490
|
+
stopLoss: p.stopLoss,
|
|
2491
|
+
takeProfit: p.takeProfit,
|
|
2492
|
+
maxQuantity: p.maxQuantity,
|
|
2493
|
+
perOrderQuantity: p.perOrderQuantity,
|
|
2494
|
+
softConditions: p.softConditions,
|
|
2495
|
+
rationale: p.rationale,
|
|
2496
|
+
createdBy: 'agent',
|
|
2497
|
+
});
|
|
2498
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }], details: {} };
|
|
2499
|
+
},
|
|
2500
|
+
},
|
|
2501
|
+
{
|
|
2502
|
+
name: 'list_strategies',
|
|
2503
|
+
label: 'List Strategies',
|
|
2504
|
+
description: 'List strategies for a thesis. Filter by status (active/watching/executed/cancelled/review) or omit for all.',
|
|
2505
|
+
parameters: Type.Object({
|
|
2506
|
+
thesisId: Type.String({ description: 'Thesis ID' }),
|
|
2507
|
+
status: Type.Optional(Type.String({ description: 'Filter by status. Omit for all.' })),
|
|
2508
|
+
}),
|
|
2509
|
+
execute: async (_id, p) => {
|
|
2510
|
+
const result = await sfClient.getStrategies(p.thesisId, p.status);
|
|
2511
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], details: {} };
|
|
2512
|
+
},
|
|
2513
|
+
},
|
|
2514
|
+
{
|
|
2515
|
+
name: 'update_strategy',
|
|
2516
|
+
label: 'Update Strategy',
|
|
2517
|
+
description: 'Update a strategy (change stop loss, take profit, status, priority, etc.)',
|
|
2518
|
+
parameters: Type.Object({
|
|
2519
|
+
thesisId: Type.String({ description: 'Thesis ID' }),
|
|
2520
|
+
strategyId: Type.String({ description: 'Strategy ID (UUID)' }),
|
|
2521
|
+
stopLoss: Type.Optional(Type.Number({ description: 'New stop loss (cents)' })),
|
|
2522
|
+
takeProfit: Type.Optional(Type.Number({ description: 'New take profit (cents)' })),
|
|
2523
|
+
entryBelow: Type.Optional(Type.Number({ description: 'New entry below trigger (cents)' })),
|
|
2524
|
+
entryAbove: Type.Optional(Type.Number({ description: 'New entry above trigger (cents)' })),
|
|
2525
|
+
status: Type.Optional(Type.String({ description: 'New status: active|watching|executed|cancelled|review' })),
|
|
2526
|
+
priority: Type.Optional(Type.Number({ description: 'New priority' })),
|
|
2527
|
+
softConditions: Type.Optional(Type.String({ description: 'Updated soft conditions' })),
|
|
2528
|
+
rationale: Type.Optional(Type.String({ description: 'Updated rationale' })),
|
|
2529
|
+
}),
|
|
2530
|
+
execute: async (_id, p) => {
|
|
2531
|
+
const { thesisId, strategyId, ...updates } = p;
|
|
2532
|
+
const result = await sfClient.updateStrategyAPI(thesisId, strategyId, updates);
|
|
2533
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }], details: {} };
|
|
2534
|
+
},
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
name: 'what_if',
|
|
2538
|
+
label: 'What-If',
|
|
2539
|
+
description: 'Run a what-if scenario: override causal tree node probabilities and see how edges and confidence change. Zero LLM cost — pure computation.',
|
|
2540
|
+
parameters: Type.Object({
|
|
2541
|
+
overrides: Type.Array(Type.Object({
|
|
2542
|
+
nodeId: Type.String({ description: 'Causal tree node ID (e.g. n1, n3.1)' }),
|
|
2543
|
+
newProbability: Type.Number({ description: 'New probability 0-1' }),
|
|
2544
|
+
}), { description: 'Node probability overrides' }),
|
|
2545
|
+
}),
|
|
2546
|
+
execute: async (_id, p) => {
|
|
2547
|
+
const ctx = latestContext;
|
|
2548
|
+
const allNodes = [];
|
|
2549
|
+
function flatten(nodes) {
|
|
2550
|
+
for (const n of nodes) {
|
|
2551
|
+
allNodes.push(n);
|
|
2552
|
+
if (n.children?.length)
|
|
2553
|
+
flatten(n.children);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
const rawNodes = ctx.causalTree?.nodes || [];
|
|
2557
|
+
flatten(rawNodes);
|
|
2558
|
+
const treeNodes = rawNodes.filter((n) => n.depth === 0 || (n.depth === undefined && !n.id.includes('.')));
|
|
2559
|
+
const overrideMap = new Map(p.overrides.map((o) => [o.nodeId, o.newProbability]));
|
|
2560
|
+
const oldConf = treeNodes.reduce((s, n) => s + (n.probability || 0) * (n.importance || 0), 0);
|
|
2561
|
+
const newConf = treeNodes.reduce((s, n) => {
|
|
2562
|
+
const prob = overrideMap.get(n.id) ?? n.probability ?? 0;
|
|
2563
|
+
return s + prob * (n.importance || 0);
|
|
2564
|
+
}, 0);
|
|
2565
|
+
const nodeScales = new Map();
|
|
2566
|
+
for (const [nid, np] of overrideMap.entries()) {
|
|
2567
|
+
const nd = allNodes.find((n) => n.id === nid);
|
|
2568
|
+
if (nd && nd.probability > 0)
|
|
2569
|
+
nodeScales.set(nid, Math.max(0, Math.min(2, np / nd.probability)));
|
|
2570
|
+
}
|
|
2571
|
+
const edges = (ctx.edges || []).map((edge) => {
|
|
2572
|
+
const relNode = edge.relatedNodeId;
|
|
2573
|
+
let scaleFactor = 1;
|
|
2574
|
+
if (relNode) {
|
|
2575
|
+
const candidates = [relNode, relNode.split('.').slice(0, -1).join('.'), relNode.split('.')[0]].filter(Boolean);
|
|
2576
|
+
for (const cid of candidates) {
|
|
2577
|
+
if (nodeScales.has(cid)) {
|
|
2578
|
+
scaleFactor = nodeScales.get(cid);
|
|
2579
|
+
break;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
const mkt = edge.marketPrice || 0;
|
|
2584
|
+
const oldTP = edge.thesisPrice || edge.thesisImpliedPrice || mkt;
|
|
2585
|
+
const oldEdge = edge.edge || edge.edgeSize || 0;
|
|
2586
|
+
const newTP = Math.round((mkt + (oldTP - mkt) * scaleFactor) * 100) / 100;
|
|
2587
|
+
const dir = edge.direction || 'yes';
|
|
2588
|
+
const newEdge = Math.round((dir === 'yes' ? newTP - mkt : mkt - newTP) * 100) / 100;
|
|
2589
|
+
return {
|
|
2590
|
+
market: edge.market || edge.marketTitle || edge.marketId,
|
|
2591
|
+
marketPrice: mkt,
|
|
2592
|
+
oldEdge,
|
|
2593
|
+
newEdge,
|
|
2594
|
+
delta: newEdge - oldEdge,
|
|
2595
|
+
signal: Math.abs(newEdge - oldEdge) < 1 ? 'unchanged' : (oldEdge > 0 && newEdge < 0) || (oldEdge < 0 && newEdge > 0) ? 'REVERSED' : Math.abs(newEdge) < 2 ? 'GONE' : 'reduced',
|
|
2596
|
+
};
|
|
2597
|
+
}).filter((e) => e.signal !== 'unchanged');
|
|
2598
|
+
const result = {
|
|
2599
|
+
overrides: p.overrides.map((o) => {
|
|
2600
|
+
const node = allNodes.find((n) => n.id === o.nodeId);
|
|
2601
|
+
return { nodeId: o.nodeId, label: node?.label || o.nodeId, oldProb: node?.probability, newProb: o.newProbability };
|
|
2602
|
+
}),
|
|
2603
|
+
confidence: { old: Math.round(oldConf * 100), new: Math.round(newConf * 100), delta: Math.round((newConf - oldConf) * 100) },
|
|
2604
|
+
affectedEdges: edges,
|
|
2605
|
+
};
|
|
2606
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], details: {} };
|
|
2607
|
+
},
|
|
2608
|
+
},
|
|
2302
2609
|
];
|
|
2610
|
+
// ── Trading tools (conditional on tradingEnabled) for plain mode ──────────
|
|
2611
|
+
const config = (0, config_js_1.loadConfig)();
|
|
2612
|
+
if (config.tradingEnabled) {
|
|
2613
|
+
tools.push({
|
|
2614
|
+
name: 'place_order',
|
|
2615
|
+
label: 'Place Order',
|
|
2616
|
+
description: 'Place a buy or sell order on Kalshi. Executes directly (no confirmation prompt in plain mode).',
|
|
2617
|
+
parameters: Type.Object({
|
|
2618
|
+
ticker: Type.String({ description: 'Market ticker e.g. KXWTIMAX-26DEC31-T135' }),
|
|
2619
|
+
side: Type.String({ description: 'yes or no' }),
|
|
2620
|
+
action: Type.String({ description: 'buy or sell' }),
|
|
2621
|
+
type: Type.String({ description: 'limit or market' }),
|
|
2622
|
+
count: Type.Number({ description: 'Number of contracts' }),
|
|
2623
|
+
price_cents: Type.Optional(Type.Number({ description: 'Limit price in cents (1-99). Required for limit orders.' })),
|
|
2624
|
+
}),
|
|
2625
|
+
execute: async (_id, p) => {
|
|
2626
|
+
const { createOrder } = await import('../kalshi.js');
|
|
2627
|
+
const priceCents = p.price_cents ? Math.round(Number(p.price_cents)) : undefined;
|
|
2628
|
+
const maxCost = ((priceCents || 99) * p.count / 100).toFixed(2);
|
|
2629
|
+
process.stderr.write(` Order: ${p.action.toUpperCase()} ${p.count}x ${p.ticker} ${p.side.toUpperCase()} @ ${priceCents ? priceCents + '\u00A2' : 'market'} (max $${maxCost})\n`);
|
|
2630
|
+
try {
|
|
2631
|
+
const result = await createOrder({
|
|
2632
|
+
ticker: p.ticker,
|
|
2633
|
+
side: p.side,
|
|
2634
|
+
action: p.action,
|
|
2635
|
+
type: p.type,
|
|
2636
|
+
count: p.count,
|
|
2637
|
+
...(priceCents ? { yes_price: priceCents } : {}),
|
|
2638
|
+
});
|
|
2639
|
+
const order = result.order || result;
|
|
2640
|
+
return {
|
|
2641
|
+
content: [{ type: 'text', text: `Order placed: ${order.order_id || 'OK'}\nStatus: ${order.status || '-'}\nFilled: ${order.fill_count_fp || 0}/${order.initial_count_fp || p.count}` }],
|
|
2642
|
+
details: {},
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
catch (err) {
|
|
2646
|
+
const msg = err.message || String(err);
|
|
2647
|
+
if (msg.includes('403')) {
|
|
2648
|
+
return { content: [{ type: 'text', text: '403 Forbidden \u2014 your Kalshi key lacks write permission. Get a read+write key at kalshi.com/account/api-keys' }], details: {} };
|
|
2649
|
+
}
|
|
2650
|
+
return { content: [{ type: 'text', text: `Order failed: ${msg}` }], details: {} };
|
|
2651
|
+
}
|
|
2652
|
+
},
|
|
2653
|
+
}, {
|
|
2654
|
+
name: 'cancel_order',
|
|
2655
|
+
label: 'Cancel Order',
|
|
2656
|
+
description: 'Cancel a resting order by order ID. Executes directly (no confirmation prompt in plain mode).',
|
|
2657
|
+
parameters: Type.Object({
|
|
2658
|
+
order_id: Type.String({ description: 'Order ID to cancel' }),
|
|
2659
|
+
}),
|
|
2660
|
+
execute: async (_id, p) => {
|
|
2661
|
+
const { cancelOrder } = await import('../kalshi.js');
|
|
2662
|
+
try {
|
|
2663
|
+
await cancelOrder(p.order_id);
|
|
2664
|
+
return { content: [{ type: 'text', text: `Order ${p.order_id} cancelled.` }], details: {} };
|
|
2665
|
+
}
|
|
2666
|
+
catch (err) {
|
|
2667
|
+
return { content: [{ type: 'text', text: `Cancel failed: ${err.message}` }], details: {} };
|
|
2668
|
+
}
|
|
2669
|
+
},
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2303
2672
|
// ── System prompt ─────────────────────────────────────────────────────────
|
|
2304
2673
|
const ctx = latestContext;
|
|
2305
2674
|
const edgesSummary = ctx.edges
|
package/dist/commands/setup.d.ts
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -71,13 +71,13 @@ async function validateSFKey(key, apiUrl) {
|
|
|
71
71
|
headers: { 'Authorization': `Bearer ${key}` },
|
|
72
72
|
});
|
|
73
73
|
if (res.ok)
|
|
74
|
-
return { valid: true, msg: `API
|
|
74
|
+
return { valid: true, msg: `API key valid — connected to ${apiUrl.replace('https://', '')}` };
|
|
75
75
|
if (res.status === 401)
|
|
76
|
-
return { valid: false, msg: '
|
|
77
|
-
return { valid: false, msg:
|
|
76
|
+
return { valid: false, msg: 'Invalid key, please try again' };
|
|
77
|
+
return { valid: false, msg: `Server returned ${res.status}` };
|
|
78
78
|
}
|
|
79
79
|
catch (err) {
|
|
80
|
-
return { valid: false, msg:
|
|
80
|
+
return { valid: false, msg: `Connection failed: ${err.message}` };
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
async function validateOpenRouterKey(key) {
|
|
@@ -86,22 +86,22 @@ async function validateOpenRouterKey(key) {
|
|
|
86
86
|
headers: { 'Authorization': `Bearer ${key}` },
|
|
87
87
|
});
|
|
88
88
|
if (res.ok)
|
|
89
|
-
return { valid: true, msg: 'OpenRouter
|
|
90
|
-
return { valid: false, msg: `OpenRouter
|
|
89
|
+
return { valid: true, msg: 'OpenRouter connected — available model: claude-sonnet-4.6' };
|
|
90
|
+
return { valid: false, msg: `OpenRouter returned ${res.status}` };
|
|
91
91
|
}
|
|
92
92
|
catch (err) {
|
|
93
|
-
return { valid: false, msg:
|
|
93
|
+
return { valid: false, msg: `Connection failed: ${err.message}` };
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
async function validateKalshi() {
|
|
97
97
|
try {
|
|
98
98
|
const positions = await (0, kalshi_js_1.getPositions)();
|
|
99
99
|
if (positions === null)
|
|
100
|
-
return { valid: false, msg: 'Kalshi
|
|
101
|
-
return { valid: true, msg: `Kalshi
|
|
100
|
+
return { valid: false, msg: 'Kalshi authentication failed', posCount: 0 };
|
|
101
|
+
return { valid: true, msg: `Kalshi authenticated — found ${positions.length} position(s)`, posCount: positions.length };
|
|
102
102
|
}
|
|
103
103
|
catch (err) {
|
|
104
|
-
return { valid: false, msg: `Kalshi
|
|
104
|
+
return { valid: false, msg: `Kalshi connection failed: ${err.message}`, posCount: 0 };
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
async function validateTavily(key) {
|
|
@@ -112,11 +112,11 @@ async function validateTavily(key) {
|
|
|
112
112
|
body: JSON.stringify({ api_key: key, query: 'test', max_results: 1 }),
|
|
113
113
|
});
|
|
114
114
|
if (res.ok)
|
|
115
|
-
return { valid: true, msg: 'Tavily
|
|
116
|
-
return { valid: false, msg: `Tavily
|
|
115
|
+
return { valid: true, msg: 'Tavily connected' };
|
|
116
|
+
return { valid: false, msg: `Tavily returned ${res.status}` };
|
|
117
117
|
}
|
|
118
118
|
catch (err) {
|
|
119
|
-
return { valid: false, msg:
|
|
119
|
+
return { valid: false, msg: `Connection failed: ${err.message}` };
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
async function setupCommand(opts) {
|
|
@@ -127,9 +127,9 @@ async function setupCommand(opts) {
|
|
|
127
127
|
// ── sf setup --reset ──────────────────────────────────────────────────────
|
|
128
128
|
if (opts.reset) {
|
|
129
129
|
(0, config_js_1.resetConfig)();
|
|
130
|
-
ok('
|
|
130
|
+
ok('Config reset');
|
|
131
131
|
blank();
|
|
132
|
-
info('
|
|
132
|
+
info('Run sf setup to reconfigure');
|
|
133
133
|
blank();
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
@@ -144,7 +144,25 @@ async function setupCommand(opts) {
|
|
|
144
144
|
const existing = (0, config_js_1.loadFileConfig)();
|
|
145
145
|
(0, config_js_1.saveConfig)({ ...existing, apiKey: opts.key, apiUrl });
|
|
146
146
|
ok(result.msg);
|
|
147
|
-
ok(
|
|
147
|
+
ok(`Saved to ${(0, config_js_1.getConfigPath)()}`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// ── sf setup --kalshi (reconfigure Kalshi credentials) ──────────────────
|
|
151
|
+
if (opts.kalshi) {
|
|
152
|
+
const existing = (0, config_js_1.loadFileConfig)();
|
|
153
|
+
blank();
|
|
154
|
+
console.log(` ${bold('Reconfigure Kalshi Credentials')}`);
|
|
155
|
+
blank();
|
|
156
|
+
info('Go to https://kalshi.com/account/api-keys to generate a new API key.');
|
|
157
|
+
info('If you need trading, make sure to enable read+write permissions.');
|
|
158
|
+
blank();
|
|
159
|
+
await promptForKalshi(existing);
|
|
160
|
+
(0, config_js_1.saveConfig)(existing);
|
|
161
|
+
if (existing.kalshiKeyId) {
|
|
162
|
+
process.env.KALSHI_API_KEY_ID = existing.kalshiKeyId;
|
|
163
|
+
process.env.KALSHI_PRIVATE_KEY_PATH = existing.kalshiPrivateKeyPath;
|
|
164
|
+
}
|
|
165
|
+
blank();
|
|
148
166
|
return;
|
|
149
167
|
}
|
|
150
168
|
// ── sf setup --enable-trading / --disable-trading ────────────────────────
|
|
@@ -169,7 +187,7 @@ async function setupCommand(opts) {
|
|
|
169
187
|
async function showCheck() {
|
|
170
188
|
const config = (0, config_js_1.loadConfig)();
|
|
171
189
|
blank();
|
|
172
|
-
console.log(` ${bold('SimpleFunctions
|
|
190
|
+
console.log(` ${bold('SimpleFunctions Config Status')}`);
|
|
173
191
|
console.log(` ${dim('─'.repeat(35))}`);
|
|
174
192
|
blank();
|
|
175
193
|
// SF API Key
|
|
@@ -177,38 +195,38 @@ async function showCheck() {
|
|
|
177
195
|
ok(`SF_API_KEY ${dim(mask(config.apiKey))}`);
|
|
178
196
|
}
|
|
179
197
|
else {
|
|
180
|
-
fail('SF_API_KEY
|
|
198
|
+
fail('SF_API_KEY not configured (required)');
|
|
181
199
|
}
|
|
182
200
|
// OpenRouter
|
|
183
201
|
if (config.openrouterKey) {
|
|
184
202
|
ok(`OPENROUTER_KEY ${dim(mask(config.openrouterKey))}`);
|
|
185
203
|
}
|
|
186
204
|
else {
|
|
187
|
-
fail(`OPENROUTER_KEY
|
|
205
|
+
fail(`OPENROUTER_KEY not configured (agent unavailable)`);
|
|
188
206
|
}
|
|
189
207
|
// Kalshi
|
|
190
208
|
if (config.kalshiKeyId && config.kalshiPrivateKeyPath) {
|
|
191
209
|
ok(`KALSHI ${dim(mask(config.kalshiKeyId))}`);
|
|
192
210
|
}
|
|
193
211
|
else {
|
|
194
|
-
info(`${dim('○')} KALSHI ${dim('
|
|
212
|
+
info(`${dim('○')} KALSHI ${dim('skipped')}`);
|
|
195
213
|
}
|
|
196
214
|
// Tavily
|
|
197
215
|
if (config.tavilyKey) {
|
|
198
216
|
ok(`TAVILY ${dim(mask(config.tavilyKey))}`);
|
|
199
217
|
}
|
|
200
218
|
else {
|
|
201
|
-
info(`${dim('○')} TAVILY ${dim('
|
|
219
|
+
info(`${dim('○')} TAVILY ${dim('skipped')}`);
|
|
202
220
|
}
|
|
203
221
|
// Trading
|
|
204
222
|
if (config.tradingEnabled) {
|
|
205
|
-
ok('TRADING
|
|
223
|
+
ok('TRADING enabled');
|
|
206
224
|
}
|
|
207
225
|
else {
|
|
208
|
-
info(`${dim('○')} TRADING ${dim('
|
|
226
|
+
info(`${dim('○')} TRADING ${dim('disabled — sf setup --enable-trading')}`);
|
|
209
227
|
}
|
|
210
228
|
blank();
|
|
211
|
-
console.log(` ${dim('
|
|
229
|
+
console.log(` ${dim('Config file: ' + (0, config_js_1.getConfigPath)())}`);
|
|
212
230
|
blank();
|
|
213
231
|
}
|
|
214
232
|
// ─── Interactive Wizard ──────────────────────────────────────────────────────
|
|
@@ -222,19 +240,19 @@ async function runWizard() {
|
|
|
222
240
|
// ════════════════════════════════════════════════════════════════════════════
|
|
223
241
|
// Step 1: SF API Key
|
|
224
242
|
// ════════════════════════════════════════════════════════════════════════════
|
|
225
|
-
console.log(` ${bold('
|
|
243
|
+
console.log(` ${bold('Step 1: API Key')}`);
|
|
226
244
|
blank();
|
|
227
245
|
const existingSfKey = process.env.SF_API_KEY || config.apiKey;
|
|
228
246
|
if (existingSfKey) {
|
|
229
247
|
const result = await validateSFKey(existingSfKey, apiUrl);
|
|
230
248
|
if (result.valid) {
|
|
231
|
-
ok(
|
|
232
|
-
info(dim('
|
|
249
|
+
ok(`Detected SF_API_KEY — ${dim(mask(existingSfKey))}`);
|
|
250
|
+
info(dim('Skipping.'));
|
|
233
251
|
config.apiKey = existingSfKey;
|
|
234
252
|
blank();
|
|
235
253
|
}
|
|
236
254
|
else {
|
|
237
|
-
fail(
|
|
255
|
+
fail(`Existing key invalid: ${result.msg}`);
|
|
238
256
|
config.apiKey = await promptForSFKey(apiUrl);
|
|
239
257
|
}
|
|
240
258
|
}
|
|
@@ -249,19 +267,19 @@ async function runWizard() {
|
|
|
249
267
|
// ════════════════════════════════════════════════════════════════════════════
|
|
250
268
|
// Step 2: OpenRouter API Key
|
|
251
269
|
// ════════════════════════════════════════════════════════════════════════════
|
|
252
|
-
console.log(` ${bold('
|
|
270
|
+
console.log(` ${bold('Step 2: AI Model (for sf agent)')}`);
|
|
253
271
|
blank();
|
|
254
272
|
const existingOrKey = process.env.OPENROUTER_API_KEY || config.openrouterKey;
|
|
255
273
|
if (existingOrKey) {
|
|
256
274
|
const result = await validateOpenRouterKey(existingOrKey);
|
|
257
275
|
if (result.valid) {
|
|
258
|
-
ok(
|
|
259
|
-
info(dim('
|
|
276
|
+
ok(`Detected OPENROUTER_API_KEY — ${dim(mask(existingOrKey))}`);
|
|
277
|
+
info(dim('Skipping.'));
|
|
260
278
|
config.openrouterKey = existingOrKey;
|
|
261
279
|
blank();
|
|
262
280
|
}
|
|
263
281
|
else {
|
|
264
|
-
fail(
|
|
282
|
+
fail(`Existing key invalid: ${result.msg}`);
|
|
265
283
|
config.openrouterKey = await promptForOpenRouterKey();
|
|
266
284
|
}
|
|
267
285
|
}
|
|
@@ -274,7 +292,7 @@ async function runWizard() {
|
|
|
274
292
|
// ════════════════════════════════════════════════════════════════════════════
|
|
275
293
|
// Step 3: Kalshi Exchange
|
|
276
294
|
// ════════════════════════════════════════════════════════════════════════════
|
|
277
|
-
console.log(` ${bold('
|
|
295
|
+
console.log(` ${bold('Step 3: Kalshi Exchange (optional)')}`);
|
|
278
296
|
blank();
|
|
279
297
|
const existingKalshiId = process.env.KALSHI_API_KEY_ID || config.kalshiKeyId;
|
|
280
298
|
const existingKalshiPath = process.env.KALSHI_PRIVATE_KEY_PATH || config.kalshiPrivateKeyPath;
|
|
@@ -284,14 +302,14 @@ async function runWizard() {
|
|
|
284
302
|
process.env.KALSHI_PRIVATE_KEY_PATH = existingKalshiPath;
|
|
285
303
|
const result = await validateKalshi();
|
|
286
304
|
if (result.valid) {
|
|
287
|
-
ok(
|
|
288
|
-
info(dim('
|
|
305
|
+
ok(`Detected Kalshi — ${dim(mask(existingKalshiId))} (${result.posCount} position(s))`);
|
|
306
|
+
info(dim('Skipping.'));
|
|
289
307
|
config.kalshiKeyId = existingKalshiId;
|
|
290
308
|
config.kalshiPrivateKeyPath = existingKalshiPath;
|
|
291
309
|
blank();
|
|
292
310
|
}
|
|
293
311
|
else {
|
|
294
|
-
fail(
|
|
312
|
+
fail(`Existing credentials invalid: ${result.msg}`);
|
|
295
313
|
await promptForKalshi(config);
|
|
296
314
|
}
|
|
297
315
|
}
|
|
@@ -302,19 +320,19 @@ async function runWizard() {
|
|
|
302
320
|
// ════════════════════════════════════════════════════════════════════════════
|
|
303
321
|
// Step 4: Tavily
|
|
304
322
|
// ════════════════════════════════════════════════════════════════════════════
|
|
305
|
-
console.log(` ${bold('
|
|
323
|
+
console.log(` ${bold('Step 4: News Search (optional)')}`);
|
|
306
324
|
blank();
|
|
307
325
|
const existingTavily = process.env.TAVILY_API_KEY || config.tavilyKey;
|
|
308
326
|
if (existingTavily) {
|
|
309
327
|
const result = await validateTavily(existingTavily);
|
|
310
328
|
if (result.valid) {
|
|
311
|
-
ok(
|
|
312
|
-
info(dim('
|
|
329
|
+
ok(`Detected TAVILY_API_KEY — ${dim(mask(existingTavily))}`);
|
|
330
|
+
info(dim('Skipping.'));
|
|
313
331
|
config.tavilyKey = existingTavily;
|
|
314
332
|
blank();
|
|
315
333
|
}
|
|
316
334
|
else {
|
|
317
|
-
fail(
|
|
335
|
+
fail(`Existing key invalid: ${result.msg}`);
|
|
318
336
|
config.tavilyKey = await promptForTavily();
|
|
319
337
|
}
|
|
320
338
|
}
|
|
@@ -328,18 +346,18 @@ async function runWizard() {
|
|
|
328
346
|
// Step 5: Trading
|
|
329
347
|
// ════════════════════════════════════════════════════════════════════════════
|
|
330
348
|
if (config.kalshiKeyId) {
|
|
331
|
-
console.log(` ${bold('
|
|
349
|
+
console.log(` ${bold('Step 5: Trading (optional)')}`);
|
|
332
350
|
blank();
|
|
333
|
-
info('
|
|
334
|
-
info('
|
|
351
|
+
info('Warning: enabling this unlocks sf buy / sf sell / sf cancel.');
|
|
352
|
+
info('Your Kalshi API key must have read+write permissions.');
|
|
335
353
|
blank();
|
|
336
|
-
const enableTrading = await promptYN('
|
|
354
|
+
const enableTrading = await promptYN(' Enable trading? (y/N) ', false);
|
|
337
355
|
config.tradingEnabled = enableTrading;
|
|
338
356
|
if (enableTrading) {
|
|
339
|
-
ok('
|
|
357
|
+
ok('Trading enabled');
|
|
340
358
|
}
|
|
341
359
|
else {
|
|
342
|
-
info(dim('
|
|
360
|
+
info(dim('Skipped. You can enable later with sf setup --enable-trading.'));
|
|
343
361
|
}
|
|
344
362
|
blank();
|
|
345
363
|
(0, config_js_1.saveConfig)(config);
|
|
@@ -348,24 +366,24 @@ async function runWizard() {
|
|
|
348
366
|
// Summary
|
|
349
367
|
// ════════════════════════════════════════════════════════════════════════════
|
|
350
368
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
351
|
-
info(
|
|
369
|
+
info(`Config saved to ${dim((0, config_js_1.getConfigPath)())}`);
|
|
352
370
|
blank();
|
|
353
371
|
if (config.apiKey)
|
|
354
|
-
ok('SF_API_KEY
|
|
372
|
+
ok('SF_API_KEY configured');
|
|
355
373
|
else
|
|
356
|
-
fail('SF_API_KEY
|
|
374
|
+
fail('SF_API_KEY not configured');
|
|
357
375
|
if (config.openrouterKey)
|
|
358
|
-
ok('OPENROUTER_KEY
|
|
376
|
+
ok('OPENROUTER_KEY configured');
|
|
359
377
|
else
|
|
360
|
-
fail('OPENROUTER_KEY
|
|
378
|
+
fail('OPENROUTER_KEY skipped');
|
|
361
379
|
if (config.kalshiKeyId)
|
|
362
|
-
ok('KALSHI
|
|
380
|
+
ok('KALSHI configured');
|
|
363
381
|
else
|
|
364
|
-
info(`${dim('○')} KALSHI
|
|
382
|
+
info(`${dim('○')} KALSHI skipped`);
|
|
365
383
|
if (config.tavilyKey)
|
|
366
|
-
ok('TAVILY
|
|
384
|
+
ok('TAVILY configured');
|
|
367
385
|
else
|
|
368
|
-
info(`${dim('○')} TAVILY
|
|
386
|
+
info(`${dim('○')} TAVILY skipped`);
|
|
369
387
|
blank();
|
|
370
388
|
// ════════════════════════════════════════════════════════════════════════════
|
|
371
389
|
// Step 5: Thesis creation
|
|
@@ -376,18 +394,18 @@ async function runWizard() {
|
|
|
376
394
|
}
|
|
377
395
|
// ─── Step prompt helpers ─────────────────────────────────────────────────────
|
|
378
396
|
async function promptForSFKey(apiUrl) {
|
|
379
|
-
info(
|
|
380
|
-
info('
|
|
397
|
+
info(`Don't have a key? Sign up at ${cyan('https://simplefunctions.dev/dashboard')}.`);
|
|
398
|
+
info('Press Enter to open browser, or paste your key:');
|
|
381
399
|
blank();
|
|
382
400
|
while (true) {
|
|
383
401
|
const answer = await prompt(' > ');
|
|
384
402
|
if (!answer) {
|
|
385
403
|
// Open browser
|
|
386
404
|
openBrowser('https://simplefunctions.dev/dashboard');
|
|
387
|
-
info(dim('
|
|
405
|
+
info(dim('Browser opened. Paste your key here once you have it:'));
|
|
388
406
|
continue;
|
|
389
407
|
}
|
|
390
|
-
info(dim('
|
|
408
|
+
info(dim('Validating...'));
|
|
391
409
|
const result = await validateSFKey(answer, apiUrl);
|
|
392
410
|
if (result.valid) {
|
|
393
411
|
ok(result.msg);
|
|
@@ -400,40 +418,40 @@ async function promptForSFKey(apiUrl) {
|
|
|
400
418
|
}
|
|
401
419
|
}
|
|
402
420
|
async function promptForOpenRouterKey() {
|
|
403
|
-
info(
|
|
404
|
-
info('
|
|
421
|
+
info(`Requires an OpenRouter API key. Get one at ${cyan('https://openrouter.ai/settings/keys')}.`);
|
|
422
|
+
info('Press Enter to skip (agent unavailable), or paste key:');
|
|
405
423
|
blank();
|
|
406
424
|
const answer = await prompt(' > ');
|
|
407
425
|
if (!answer) {
|
|
408
|
-
info(dim('
|
|
426
|
+
info(dim('Skipped.'));
|
|
409
427
|
blank();
|
|
410
428
|
return undefined;
|
|
411
429
|
}
|
|
412
|
-
info(dim('
|
|
430
|
+
info(dim('Validating...'));
|
|
413
431
|
const result = await validateOpenRouterKey(answer);
|
|
414
432
|
if (result.valid) {
|
|
415
433
|
ok(result.msg);
|
|
416
434
|
}
|
|
417
435
|
else {
|
|
418
436
|
fail(result.msg);
|
|
419
|
-
info(dim('
|
|
437
|
+
info(dim('Saved. You can re-run sf setup later to fix this.'));
|
|
420
438
|
}
|
|
421
439
|
blank();
|
|
422
440
|
return answer;
|
|
423
441
|
}
|
|
424
442
|
async function promptForKalshi(config) {
|
|
425
|
-
info(
|
|
426
|
-
info(
|
|
427
|
-
info(
|
|
428
|
-
info('
|
|
443
|
+
info(`Connect Kalshi to view your positions and P&L.`);
|
|
444
|
+
info(`Requires an API Key ID and private key file.`);
|
|
445
|
+
info(`Get them at ${cyan('https://kalshi.com/account/api-keys')}.`);
|
|
446
|
+
info('Press Enter to skip, or paste Key ID:');
|
|
429
447
|
blank();
|
|
430
448
|
const keyId = await prompt(' > ');
|
|
431
449
|
if (!keyId) {
|
|
432
|
-
info(dim('
|
|
450
|
+
info(dim('Skipped.'));
|
|
433
451
|
blank();
|
|
434
452
|
return;
|
|
435
453
|
}
|
|
436
|
-
info('
|
|
454
|
+
info('Private key file path (default ~/.kalshi/private.pem):');
|
|
437
455
|
const keyPathInput = await prompt(' > ');
|
|
438
456
|
const keyPath = keyPathInput || '~/.kalshi/private.pem';
|
|
439
457
|
config.kalshiKeyId = keyId;
|
|
@@ -441,36 +459,36 @@ async function promptForKalshi(config) {
|
|
|
441
459
|
// Temporarily set for validation
|
|
442
460
|
process.env.KALSHI_API_KEY_ID = keyId;
|
|
443
461
|
process.env.KALSHI_PRIVATE_KEY_PATH = keyPath;
|
|
444
|
-
info(dim('
|
|
462
|
+
info(dim('Validating...'));
|
|
445
463
|
const result = await validateKalshi();
|
|
446
464
|
if (result.valid) {
|
|
447
465
|
ok(result.msg);
|
|
448
466
|
}
|
|
449
467
|
else {
|
|
450
468
|
fail(result.msg);
|
|
451
|
-
info(dim('
|
|
469
|
+
info(dim('Saved. You can re-run sf setup later to fix this.'));
|
|
452
470
|
}
|
|
453
471
|
blank();
|
|
454
472
|
}
|
|
455
473
|
async function promptForTavily() {
|
|
456
|
-
info(`Tavily API
|
|
457
|
-
info(
|
|
458
|
-
info('
|
|
474
|
+
info(`Tavily API powers the agent's web_search tool.`);
|
|
475
|
+
info(`Get a free key at ${cyan('https://tavily.com')}.`);
|
|
476
|
+
info('Press Enter to skip:');
|
|
459
477
|
blank();
|
|
460
478
|
const answer = await prompt(' > ');
|
|
461
479
|
if (!answer) {
|
|
462
|
-
info(dim('
|
|
480
|
+
info(dim('Skipped.'));
|
|
463
481
|
blank();
|
|
464
482
|
return undefined;
|
|
465
483
|
}
|
|
466
|
-
info(dim('
|
|
484
|
+
info(dim('Validating...'));
|
|
467
485
|
const result = await validateTavily(answer);
|
|
468
486
|
if (result.valid) {
|
|
469
487
|
ok(result.msg);
|
|
470
488
|
}
|
|
471
489
|
else {
|
|
472
490
|
fail(result.msg);
|
|
473
|
-
info(dim('
|
|
491
|
+
info(dim('Saved. You can re-run sf setup later to fix this.'));
|
|
474
492
|
}
|
|
475
493
|
blank();
|
|
476
494
|
return answer;
|
|
@@ -483,67 +501,67 @@ async function handleThesisStep(config) {
|
|
|
483
501
|
const theses = data.theses || [];
|
|
484
502
|
const activeTheses = theses.filter((t) => t.status === 'active');
|
|
485
503
|
if (activeTheses.length > 0) {
|
|
486
|
-
console.log(` ${bold('
|
|
504
|
+
console.log(` ${bold('Step 6: Theses')}`);
|
|
487
505
|
blank();
|
|
488
|
-
ok(
|
|
506
|
+
ok(`Found ${activeTheses.length} active thesis(es):`);
|
|
489
507
|
for (const t of activeTheses.slice(0, 5)) {
|
|
490
508
|
const conf = typeof t.confidence === 'number' ? Math.round(t.confidence * 100) : 0;
|
|
491
509
|
const thesis = (t.rawThesis || t.thesis || t.title || '').slice(0, 60);
|
|
492
510
|
info(` ${dim(t.id.slice(0, 8))} — ${thesis} — ${conf}%`);
|
|
493
511
|
}
|
|
494
|
-
info(dim('
|
|
512
|
+
info(dim('Skipping creation.'));
|
|
495
513
|
blank();
|
|
496
514
|
// Offer to launch agent
|
|
497
515
|
if (config.openrouterKey) {
|
|
498
516
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
499
|
-
console.log(` ${bold('
|
|
517
|
+
console.log(` ${bold('All set!')}`);
|
|
500
518
|
blank();
|
|
501
|
-
info(` ${cyan('sf agent')}
|
|
502
|
-
info(` ${cyan('sf context <id>')}
|
|
503
|
-
info(` ${cyan('sf positions')}
|
|
504
|
-
info(` ${cyan('sf setup --check')}
|
|
519
|
+
info(` ${cyan('sf agent')} Chat with your thesis`);
|
|
520
|
+
info(` ${cyan('sf context <id>')} View thesis snapshot`);
|
|
521
|
+
info(` ${cyan('sf positions')} View positions`);
|
|
522
|
+
info(` ${cyan('sf setup --check')} Check config`);
|
|
505
523
|
blank();
|
|
506
|
-
const shouldLaunch = await promptYN(`
|
|
524
|
+
const shouldLaunch = await promptYN(` Launch agent now? (Y/n) `);
|
|
507
525
|
if (shouldLaunch) {
|
|
508
526
|
blank();
|
|
509
|
-
info('
|
|
527
|
+
info('Launching...');
|
|
510
528
|
blank();
|
|
511
529
|
await (0, agent_js_1.agentCommand)(activeTheses[0].id, { model: config.model });
|
|
512
530
|
}
|
|
513
531
|
}
|
|
514
532
|
else {
|
|
515
533
|
blank();
|
|
516
|
-
console.log(` ${bold('
|
|
534
|
+
console.log(` ${bold('All set!')}`);
|
|
517
535
|
blank();
|
|
518
|
-
info(` ${cyan('sf list')}
|
|
519
|
-
info(` ${cyan('sf context <id>')}
|
|
520
|
-
info(` ${cyan('sf positions')}
|
|
521
|
-
info(` ${cyan('sf setup --check')}
|
|
536
|
+
info(` ${cyan('sf list')} List all theses`);
|
|
537
|
+
info(` ${cyan('sf context <id>')} View thesis snapshot`);
|
|
538
|
+
info(` ${cyan('sf positions')} View positions`);
|
|
539
|
+
info(` ${cyan('sf setup --check')} Check config`);
|
|
522
540
|
blank();
|
|
523
541
|
}
|
|
524
542
|
return;
|
|
525
543
|
}
|
|
526
544
|
// No theses — offer to create one
|
|
527
|
-
console.log(` ${bold('
|
|
545
|
+
console.log(` ${bold('Step 6: Create Your First Thesis')}`);
|
|
528
546
|
blank();
|
|
529
|
-
info('
|
|
530
|
-
info('
|
|
547
|
+
info('A thesis is your core market conviction. The system builds a causal model');
|
|
548
|
+
info('from it, then continuously scans prediction markets for mispriced contracts.');
|
|
531
549
|
blank();
|
|
532
|
-
info('
|
|
533
|
-
info(` ${dim('"
|
|
534
|
-
info(` ${dim('"AI
|
|
535
|
-
info(` ${dim('"Trump
|
|
550
|
+
info('Examples:');
|
|
551
|
+
info(` ${dim('"The Fed won\'t cut rates in 2026 — inflation stays elevated due to oil prices"')}`);
|
|
552
|
+
info(` ${dim('"AI-driven layoffs cause consumer spending to contract, S&P drops 20% by year-end"')}`);
|
|
553
|
+
info(` ${dim('"Trump can\'t exit the Iran conflict — oil stays above $100 for six months"')}`);
|
|
536
554
|
blank();
|
|
537
|
-
const thesis = await prompt('
|
|
555
|
+
const thesis = await prompt(' Enter your thesis (press Enter to skip, use sf create later):\n > ');
|
|
538
556
|
if (!thesis) {
|
|
539
557
|
blank();
|
|
540
|
-
info(dim('
|
|
558
|
+
info(dim('Skipped. Use sf create "your thesis" to create one later.'));
|
|
541
559
|
blank();
|
|
542
560
|
showFinalHints(config);
|
|
543
561
|
return;
|
|
544
562
|
}
|
|
545
563
|
blank();
|
|
546
|
-
info('
|
|
564
|
+
info('Building causal model... (~30s)');
|
|
547
565
|
blank();
|
|
548
566
|
try {
|
|
549
567
|
const result = await client.createThesis(thesis, true);
|
|
@@ -552,20 +570,20 @@ async function handleThesisStep(config) {
|
|
|
552
570
|
const edgeCount = result.edgeAnalysis?.edges?.length || 0;
|
|
553
571
|
const totalMarkets = result.edgeAnalysis?.totalMarketsAnalyzed || 0;
|
|
554
572
|
const confidence = Math.round((parseFloat(result.confidence) || 0.5) * 100);
|
|
555
|
-
ok(
|
|
556
|
-
ok(
|
|
557
|
-
ok(
|
|
558
|
-
ok(
|
|
573
|
+
ok(`Causal tree: ${nodeCount} node(s)`);
|
|
574
|
+
ok(`Scanned ${totalMarkets} markets, found ${edgeCount} contract(s) with edge`);
|
|
575
|
+
ok(`Confidence: ${confidence}%`);
|
|
576
|
+
ok(`Thesis ID: ${result.id.slice(0, 8)}`);
|
|
559
577
|
blank();
|
|
560
578
|
// Offer to launch agent
|
|
561
579
|
if (config.openrouterKey) {
|
|
562
580
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
563
|
-
console.log(` ${bold('
|
|
581
|
+
console.log(` ${bold('All set!')}`);
|
|
564
582
|
blank();
|
|
565
|
-
const shouldLaunch = await promptYN(`
|
|
583
|
+
const shouldLaunch = await promptYN(` Launch agent now? (Y/n) `);
|
|
566
584
|
if (shouldLaunch) {
|
|
567
585
|
blank();
|
|
568
|
-
info('
|
|
586
|
+
info('Launching...');
|
|
569
587
|
blank();
|
|
570
588
|
await (0, agent_js_1.agentCommand)(result.id, { model: config.model });
|
|
571
589
|
}
|
|
@@ -579,15 +597,15 @@ async function handleThesisStep(config) {
|
|
|
579
597
|
}
|
|
580
598
|
}
|
|
581
599
|
else {
|
|
582
|
-
fail(
|
|
583
|
-
info(dim('
|
|
600
|
+
fail(`Creation failed: ${result.error || 'unknown error'}`);
|
|
601
|
+
info(dim('You can retry later with sf create "your thesis"'));
|
|
584
602
|
blank();
|
|
585
603
|
showFinalHints(config);
|
|
586
604
|
}
|
|
587
605
|
}
|
|
588
606
|
catch (err) {
|
|
589
|
-
fail(
|
|
590
|
-
info(dim('
|
|
607
|
+
fail(`Creation failed: ${err.message}`);
|
|
608
|
+
info(dim('You can retry later with sf create "your thesis"'));
|
|
591
609
|
blank();
|
|
592
610
|
showFinalHints(config);
|
|
593
611
|
}
|
|
@@ -600,12 +618,12 @@ async function handleThesisStep(config) {
|
|
|
600
618
|
}
|
|
601
619
|
function showFinalHints(config) {
|
|
602
620
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
603
|
-
console.log(` ${bold('
|
|
621
|
+
console.log(` ${bold('All set!')}`);
|
|
604
622
|
blank();
|
|
605
|
-
info(` ${cyan('sf agent')}
|
|
606
|
-
info(` ${cyan('sf list')}
|
|
607
|
-
info(` ${cyan('sf context <id>')}
|
|
608
|
-
info(` ${cyan('sf positions')}
|
|
609
|
-
info(` ${cyan('sf setup --check')}
|
|
623
|
+
info(` ${cyan('sf agent')} Chat with your thesis`);
|
|
624
|
+
info(` ${cyan('sf list')} List all theses`);
|
|
625
|
+
info(` ${cyan('sf context <id>')} View thesis snapshot`);
|
|
626
|
+
info(` ${cyan('sf positions')} View positions`);
|
|
627
|
+
info(` ${cyan('sf setup --check')} Check config`);
|
|
610
628
|
blank();
|
|
611
629
|
}
|
package/dist/commands/trade.js
CHANGED
|
@@ -26,7 +26,6 @@ async function executeOrder(ticker, qty, action, opts) {
|
|
|
26
26
|
if (priceCents !== undefined && (priceCents < 1 || priceCents > 99)) {
|
|
27
27
|
throw new Error('Price must be 1-99 cents.');
|
|
28
28
|
}
|
|
29
|
-
const priceDollars = priceCents ? (priceCents / 100).toFixed(2) : undefined;
|
|
30
29
|
const maxCost = ((priceCents || 99) * quantity / 100).toFixed(2);
|
|
31
30
|
console.log();
|
|
32
31
|
console.log(` ${utils_js_1.c.bold}${utils_js_1.c.cyan}${action.toUpperCase()} Order${utils_js_1.c.reset}`);
|
|
@@ -35,7 +34,7 @@ async function executeOrder(ticker, qty, action, opts) {
|
|
|
35
34
|
console.log(` Side: ${side === 'yes' ? utils_js_1.c.green + 'YES' + utils_js_1.c.reset : utils_js_1.c.red + 'NO' + utils_js_1.c.reset}`);
|
|
36
35
|
console.log(` Quantity: ${quantity}`);
|
|
37
36
|
console.log(` Type: ${orderType}`);
|
|
38
|
-
if (
|
|
37
|
+
if (priceCents)
|
|
39
38
|
console.log(` Price: ${priceCents}¢`);
|
|
40
39
|
console.log(` Max cost: $${maxCost}`);
|
|
41
40
|
console.log();
|
|
@@ -53,7 +52,7 @@ async function executeOrder(ticker, qty, action, opts) {
|
|
|
53
52
|
action,
|
|
54
53
|
type: orderType,
|
|
55
54
|
count: quantity,
|
|
56
|
-
...(
|
|
55
|
+
...(priceCents ? { yes_price: priceCents } : {}),
|
|
57
56
|
});
|
|
58
57
|
const order = result.order || result;
|
|
59
58
|
console.log();
|
package/dist/index.js
CHANGED
|
@@ -63,8 +63,8 @@ program
|
|
|
63
63
|
.option('--api-url <url>', 'API base URL (or set SF_API_URL env var)');
|
|
64
64
|
// ── Pre-action guard: check configuration ────────────────────────────────────
|
|
65
65
|
const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan', 'explore', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history']);
|
|
66
|
-
program.hook('preAction', (thisCommand) => {
|
|
67
|
-
const cmdName =
|
|
66
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
67
|
+
const cmdName = actionCommand.name();
|
|
68
68
|
if (NO_CONFIG_COMMANDS.has(cmdName))
|
|
69
69
|
return;
|
|
70
70
|
// --api-key flag overrides config check
|
|
@@ -87,8 +87,9 @@ program
|
|
|
87
87
|
.option('--key <key>', 'Set SF API key (non-interactive, for CI)')
|
|
88
88
|
.option('--enable-trading', 'Enable trading (sf buy/sell/cancel)')
|
|
89
89
|
.option('--disable-trading', 'Disable trading')
|
|
90
|
+
.option('--kalshi', 'Reconfigure Kalshi API credentials')
|
|
90
91
|
.action(async (opts) => {
|
|
91
|
-
await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading }));
|
|
92
|
+
await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading, kalshi: opts.kalshi }));
|
|
92
93
|
});
|
|
93
94
|
// ── sf list ──────────────────────────────────────────────────────────────────
|
|
94
95
|
program
|
package/dist/kalshi.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfunctions/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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"
|
|
@@ -39,6 +39,6 @@
|
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "https://github.com/
|
|
42
|
+
"url": "https://github.com/spfunctions/simplefunctions-cli"
|
|
43
43
|
}
|
|
44
44
|
}
|