@spfunctions/cli 1.2.1 → 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 +365 -0
- package/dist/commands/setup.js +123 -123
- package/dist/index.js +2 -2
- 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({
|
|
@@ -2303,7 +2370,305 @@ async function runPlainTextAgent(params) {
|
|
|
2303
2370
|
}
|
|
2304
2371
|
},
|
|
2305
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
|
+
},
|
|
2306
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
|
+
}
|
|
2307
2672
|
// ── System prompt ─────────────────────────────────────────────────────────
|
|
2308
2673
|
const ctx = latestContext;
|
|
2309
2674
|
const edgesSummary = ctx.edges
|
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,17 +144,17 @@ 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
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
// ── sf setup --kalshi (reconfigure Kalshi credentials) ──────────────────
|
|
151
151
|
if (opts.kalshi) {
|
|
152
152
|
const existing = (0, config_js_1.loadFileConfig)();
|
|
153
153
|
blank();
|
|
154
|
-
console.log(` ${bold('
|
|
154
|
+
console.log(` ${bold('Reconfigure Kalshi Credentials')}`);
|
|
155
155
|
blank();
|
|
156
|
-
info('
|
|
157
|
-
info('
|
|
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
158
|
blank();
|
|
159
159
|
await promptForKalshi(existing);
|
|
160
160
|
(0, config_js_1.saveConfig)(existing);
|
|
@@ -187,7 +187,7 @@ async function setupCommand(opts) {
|
|
|
187
187
|
async function showCheck() {
|
|
188
188
|
const config = (0, config_js_1.loadConfig)();
|
|
189
189
|
blank();
|
|
190
|
-
console.log(` ${bold('SimpleFunctions
|
|
190
|
+
console.log(` ${bold('SimpleFunctions Config Status')}`);
|
|
191
191
|
console.log(` ${dim('─'.repeat(35))}`);
|
|
192
192
|
blank();
|
|
193
193
|
// SF API Key
|
|
@@ -195,38 +195,38 @@ async function showCheck() {
|
|
|
195
195
|
ok(`SF_API_KEY ${dim(mask(config.apiKey))}`);
|
|
196
196
|
}
|
|
197
197
|
else {
|
|
198
|
-
fail('SF_API_KEY
|
|
198
|
+
fail('SF_API_KEY not configured (required)');
|
|
199
199
|
}
|
|
200
200
|
// OpenRouter
|
|
201
201
|
if (config.openrouterKey) {
|
|
202
202
|
ok(`OPENROUTER_KEY ${dim(mask(config.openrouterKey))}`);
|
|
203
203
|
}
|
|
204
204
|
else {
|
|
205
|
-
fail(`OPENROUTER_KEY
|
|
205
|
+
fail(`OPENROUTER_KEY not configured (agent unavailable)`);
|
|
206
206
|
}
|
|
207
207
|
// Kalshi
|
|
208
208
|
if (config.kalshiKeyId && config.kalshiPrivateKeyPath) {
|
|
209
209
|
ok(`KALSHI ${dim(mask(config.kalshiKeyId))}`);
|
|
210
210
|
}
|
|
211
211
|
else {
|
|
212
|
-
info(`${dim('○')} KALSHI ${dim('
|
|
212
|
+
info(`${dim('○')} KALSHI ${dim('skipped')}`);
|
|
213
213
|
}
|
|
214
214
|
// Tavily
|
|
215
215
|
if (config.tavilyKey) {
|
|
216
216
|
ok(`TAVILY ${dim(mask(config.tavilyKey))}`);
|
|
217
217
|
}
|
|
218
218
|
else {
|
|
219
|
-
info(`${dim('○')} TAVILY ${dim('
|
|
219
|
+
info(`${dim('○')} TAVILY ${dim('skipped')}`);
|
|
220
220
|
}
|
|
221
221
|
// Trading
|
|
222
222
|
if (config.tradingEnabled) {
|
|
223
|
-
ok('TRADING
|
|
223
|
+
ok('TRADING enabled');
|
|
224
224
|
}
|
|
225
225
|
else {
|
|
226
|
-
info(`${dim('○')} TRADING ${dim('
|
|
226
|
+
info(`${dim('○')} TRADING ${dim('disabled — sf setup --enable-trading')}`);
|
|
227
227
|
}
|
|
228
228
|
blank();
|
|
229
|
-
console.log(` ${dim('
|
|
229
|
+
console.log(` ${dim('Config file: ' + (0, config_js_1.getConfigPath)())}`);
|
|
230
230
|
blank();
|
|
231
231
|
}
|
|
232
232
|
// ─── Interactive Wizard ──────────────────────────────────────────────────────
|
|
@@ -240,19 +240,19 @@ async function runWizard() {
|
|
|
240
240
|
// ════════════════════════════════════════════════════════════════════════════
|
|
241
241
|
// Step 1: SF API Key
|
|
242
242
|
// ════════════════════════════════════════════════════════════════════════════
|
|
243
|
-
console.log(` ${bold('
|
|
243
|
+
console.log(` ${bold('Step 1: API Key')}`);
|
|
244
244
|
blank();
|
|
245
245
|
const existingSfKey = process.env.SF_API_KEY || config.apiKey;
|
|
246
246
|
if (existingSfKey) {
|
|
247
247
|
const result = await validateSFKey(existingSfKey, apiUrl);
|
|
248
248
|
if (result.valid) {
|
|
249
|
-
ok(
|
|
250
|
-
info(dim('
|
|
249
|
+
ok(`Detected SF_API_KEY — ${dim(mask(existingSfKey))}`);
|
|
250
|
+
info(dim('Skipping.'));
|
|
251
251
|
config.apiKey = existingSfKey;
|
|
252
252
|
blank();
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
|
-
fail(
|
|
255
|
+
fail(`Existing key invalid: ${result.msg}`);
|
|
256
256
|
config.apiKey = await promptForSFKey(apiUrl);
|
|
257
257
|
}
|
|
258
258
|
}
|
|
@@ -267,19 +267,19 @@ async function runWizard() {
|
|
|
267
267
|
// ════════════════════════════════════════════════════════════════════════════
|
|
268
268
|
// Step 2: OpenRouter API Key
|
|
269
269
|
// ════════════════════════════════════════════════════════════════════════════
|
|
270
|
-
console.log(` ${bold('
|
|
270
|
+
console.log(` ${bold('Step 2: AI Model (for sf agent)')}`);
|
|
271
271
|
blank();
|
|
272
272
|
const existingOrKey = process.env.OPENROUTER_API_KEY || config.openrouterKey;
|
|
273
273
|
if (existingOrKey) {
|
|
274
274
|
const result = await validateOpenRouterKey(existingOrKey);
|
|
275
275
|
if (result.valid) {
|
|
276
|
-
ok(
|
|
277
|
-
info(dim('
|
|
276
|
+
ok(`Detected OPENROUTER_API_KEY — ${dim(mask(existingOrKey))}`);
|
|
277
|
+
info(dim('Skipping.'));
|
|
278
278
|
config.openrouterKey = existingOrKey;
|
|
279
279
|
blank();
|
|
280
280
|
}
|
|
281
281
|
else {
|
|
282
|
-
fail(
|
|
282
|
+
fail(`Existing key invalid: ${result.msg}`);
|
|
283
283
|
config.openrouterKey = await promptForOpenRouterKey();
|
|
284
284
|
}
|
|
285
285
|
}
|
|
@@ -292,7 +292,7 @@ async function runWizard() {
|
|
|
292
292
|
// ════════════════════════════════════════════════════════════════════════════
|
|
293
293
|
// Step 3: Kalshi Exchange
|
|
294
294
|
// ════════════════════════════════════════════════════════════════════════════
|
|
295
|
-
console.log(` ${bold('
|
|
295
|
+
console.log(` ${bold('Step 3: Kalshi Exchange (optional)')}`);
|
|
296
296
|
blank();
|
|
297
297
|
const existingKalshiId = process.env.KALSHI_API_KEY_ID || config.kalshiKeyId;
|
|
298
298
|
const existingKalshiPath = process.env.KALSHI_PRIVATE_KEY_PATH || config.kalshiPrivateKeyPath;
|
|
@@ -302,14 +302,14 @@ async function runWizard() {
|
|
|
302
302
|
process.env.KALSHI_PRIVATE_KEY_PATH = existingKalshiPath;
|
|
303
303
|
const result = await validateKalshi();
|
|
304
304
|
if (result.valid) {
|
|
305
|
-
ok(
|
|
306
|
-
info(dim('
|
|
305
|
+
ok(`Detected Kalshi — ${dim(mask(existingKalshiId))} (${result.posCount} position(s))`);
|
|
306
|
+
info(dim('Skipping.'));
|
|
307
307
|
config.kalshiKeyId = existingKalshiId;
|
|
308
308
|
config.kalshiPrivateKeyPath = existingKalshiPath;
|
|
309
309
|
blank();
|
|
310
310
|
}
|
|
311
311
|
else {
|
|
312
|
-
fail(
|
|
312
|
+
fail(`Existing credentials invalid: ${result.msg}`);
|
|
313
313
|
await promptForKalshi(config);
|
|
314
314
|
}
|
|
315
315
|
}
|
|
@@ -320,19 +320,19 @@ async function runWizard() {
|
|
|
320
320
|
// ════════════════════════════════════════════════════════════════════════════
|
|
321
321
|
// Step 4: Tavily
|
|
322
322
|
// ════════════════════════════════════════════════════════════════════════════
|
|
323
|
-
console.log(` ${bold('
|
|
323
|
+
console.log(` ${bold('Step 4: News Search (optional)')}`);
|
|
324
324
|
blank();
|
|
325
325
|
const existingTavily = process.env.TAVILY_API_KEY || config.tavilyKey;
|
|
326
326
|
if (existingTavily) {
|
|
327
327
|
const result = await validateTavily(existingTavily);
|
|
328
328
|
if (result.valid) {
|
|
329
|
-
ok(
|
|
330
|
-
info(dim('
|
|
329
|
+
ok(`Detected TAVILY_API_KEY — ${dim(mask(existingTavily))}`);
|
|
330
|
+
info(dim('Skipping.'));
|
|
331
331
|
config.tavilyKey = existingTavily;
|
|
332
332
|
blank();
|
|
333
333
|
}
|
|
334
334
|
else {
|
|
335
|
-
fail(
|
|
335
|
+
fail(`Existing key invalid: ${result.msg}`);
|
|
336
336
|
config.tavilyKey = await promptForTavily();
|
|
337
337
|
}
|
|
338
338
|
}
|
|
@@ -346,18 +346,18 @@ async function runWizard() {
|
|
|
346
346
|
// Step 5: Trading
|
|
347
347
|
// ════════════════════════════════════════════════════════════════════════════
|
|
348
348
|
if (config.kalshiKeyId) {
|
|
349
|
-
console.log(` ${bold('
|
|
349
|
+
console.log(` ${bold('Step 5: Trading (optional)')}`);
|
|
350
350
|
blank();
|
|
351
|
-
info('
|
|
352
|
-
info('
|
|
351
|
+
info('Warning: enabling this unlocks sf buy / sf sell / sf cancel.');
|
|
352
|
+
info('Your Kalshi API key must have read+write permissions.');
|
|
353
353
|
blank();
|
|
354
|
-
const enableTrading = await promptYN('
|
|
354
|
+
const enableTrading = await promptYN(' Enable trading? (y/N) ', false);
|
|
355
355
|
config.tradingEnabled = enableTrading;
|
|
356
356
|
if (enableTrading) {
|
|
357
|
-
ok('
|
|
357
|
+
ok('Trading enabled');
|
|
358
358
|
}
|
|
359
359
|
else {
|
|
360
|
-
info(dim('
|
|
360
|
+
info(dim('Skipped. You can enable later with sf setup --enable-trading.'));
|
|
361
361
|
}
|
|
362
362
|
blank();
|
|
363
363
|
(0, config_js_1.saveConfig)(config);
|
|
@@ -366,24 +366,24 @@ async function runWizard() {
|
|
|
366
366
|
// Summary
|
|
367
367
|
// ════════════════════════════════════════════════════════════════════════════
|
|
368
368
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
369
|
-
info(
|
|
369
|
+
info(`Config saved to ${dim((0, config_js_1.getConfigPath)())}`);
|
|
370
370
|
blank();
|
|
371
371
|
if (config.apiKey)
|
|
372
|
-
ok('SF_API_KEY
|
|
372
|
+
ok('SF_API_KEY configured');
|
|
373
373
|
else
|
|
374
|
-
fail('SF_API_KEY
|
|
374
|
+
fail('SF_API_KEY not configured');
|
|
375
375
|
if (config.openrouterKey)
|
|
376
|
-
ok('OPENROUTER_KEY
|
|
376
|
+
ok('OPENROUTER_KEY configured');
|
|
377
377
|
else
|
|
378
|
-
fail('OPENROUTER_KEY
|
|
378
|
+
fail('OPENROUTER_KEY skipped');
|
|
379
379
|
if (config.kalshiKeyId)
|
|
380
|
-
ok('KALSHI
|
|
380
|
+
ok('KALSHI configured');
|
|
381
381
|
else
|
|
382
|
-
info(`${dim('○')} KALSHI
|
|
382
|
+
info(`${dim('○')} KALSHI skipped`);
|
|
383
383
|
if (config.tavilyKey)
|
|
384
|
-
ok('TAVILY
|
|
384
|
+
ok('TAVILY configured');
|
|
385
385
|
else
|
|
386
|
-
info(`${dim('○')} TAVILY
|
|
386
|
+
info(`${dim('○')} TAVILY skipped`);
|
|
387
387
|
blank();
|
|
388
388
|
// ════════════════════════════════════════════════════════════════════════════
|
|
389
389
|
// Step 5: Thesis creation
|
|
@@ -394,18 +394,18 @@ async function runWizard() {
|
|
|
394
394
|
}
|
|
395
395
|
// ─── Step prompt helpers ─────────────────────────────────────────────────────
|
|
396
396
|
async function promptForSFKey(apiUrl) {
|
|
397
|
-
info(
|
|
398
|
-
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:');
|
|
399
399
|
blank();
|
|
400
400
|
while (true) {
|
|
401
401
|
const answer = await prompt(' > ');
|
|
402
402
|
if (!answer) {
|
|
403
403
|
// Open browser
|
|
404
404
|
openBrowser('https://simplefunctions.dev/dashboard');
|
|
405
|
-
info(dim('
|
|
405
|
+
info(dim('Browser opened. Paste your key here once you have it:'));
|
|
406
406
|
continue;
|
|
407
407
|
}
|
|
408
|
-
info(dim('
|
|
408
|
+
info(dim('Validating...'));
|
|
409
409
|
const result = await validateSFKey(answer, apiUrl);
|
|
410
410
|
if (result.valid) {
|
|
411
411
|
ok(result.msg);
|
|
@@ -418,40 +418,40 @@ async function promptForSFKey(apiUrl) {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
async function promptForOpenRouterKey() {
|
|
421
|
-
info(
|
|
422
|
-
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:');
|
|
423
423
|
blank();
|
|
424
424
|
const answer = await prompt(' > ');
|
|
425
425
|
if (!answer) {
|
|
426
|
-
info(dim('
|
|
426
|
+
info(dim('Skipped.'));
|
|
427
427
|
blank();
|
|
428
428
|
return undefined;
|
|
429
429
|
}
|
|
430
|
-
info(dim('
|
|
430
|
+
info(dim('Validating...'));
|
|
431
431
|
const result = await validateOpenRouterKey(answer);
|
|
432
432
|
if (result.valid) {
|
|
433
433
|
ok(result.msg);
|
|
434
434
|
}
|
|
435
435
|
else {
|
|
436
436
|
fail(result.msg);
|
|
437
|
-
info(dim('
|
|
437
|
+
info(dim('Saved. You can re-run sf setup later to fix this.'));
|
|
438
438
|
}
|
|
439
439
|
blank();
|
|
440
440
|
return answer;
|
|
441
441
|
}
|
|
442
442
|
async function promptForKalshi(config) {
|
|
443
|
-
info(
|
|
444
|
-
info(
|
|
445
|
-
info(
|
|
446
|
-
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:');
|
|
447
447
|
blank();
|
|
448
448
|
const keyId = await prompt(' > ');
|
|
449
449
|
if (!keyId) {
|
|
450
|
-
info(dim('
|
|
450
|
+
info(dim('Skipped.'));
|
|
451
451
|
blank();
|
|
452
452
|
return;
|
|
453
453
|
}
|
|
454
|
-
info('
|
|
454
|
+
info('Private key file path (default ~/.kalshi/private.pem):');
|
|
455
455
|
const keyPathInput = await prompt(' > ');
|
|
456
456
|
const keyPath = keyPathInput || '~/.kalshi/private.pem';
|
|
457
457
|
config.kalshiKeyId = keyId;
|
|
@@ -459,36 +459,36 @@ async function promptForKalshi(config) {
|
|
|
459
459
|
// Temporarily set for validation
|
|
460
460
|
process.env.KALSHI_API_KEY_ID = keyId;
|
|
461
461
|
process.env.KALSHI_PRIVATE_KEY_PATH = keyPath;
|
|
462
|
-
info(dim('
|
|
462
|
+
info(dim('Validating...'));
|
|
463
463
|
const result = await validateKalshi();
|
|
464
464
|
if (result.valid) {
|
|
465
465
|
ok(result.msg);
|
|
466
466
|
}
|
|
467
467
|
else {
|
|
468
468
|
fail(result.msg);
|
|
469
|
-
info(dim('
|
|
469
|
+
info(dim('Saved. You can re-run sf setup later to fix this.'));
|
|
470
470
|
}
|
|
471
471
|
blank();
|
|
472
472
|
}
|
|
473
473
|
async function promptForTavily() {
|
|
474
|
-
info(`Tavily API
|
|
475
|
-
info(
|
|
476
|
-
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:');
|
|
477
477
|
blank();
|
|
478
478
|
const answer = await prompt(' > ');
|
|
479
479
|
if (!answer) {
|
|
480
|
-
info(dim('
|
|
480
|
+
info(dim('Skipped.'));
|
|
481
481
|
blank();
|
|
482
482
|
return undefined;
|
|
483
483
|
}
|
|
484
|
-
info(dim('
|
|
484
|
+
info(dim('Validating...'));
|
|
485
485
|
const result = await validateTavily(answer);
|
|
486
486
|
if (result.valid) {
|
|
487
487
|
ok(result.msg);
|
|
488
488
|
}
|
|
489
489
|
else {
|
|
490
490
|
fail(result.msg);
|
|
491
|
-
info(dim('
|
|
491
|
+
info(dim('Saved. You can re-run sf setup later to fix this.'));
|
|
492
492
|
}
|
|
493
493
|
blank();
|
|
494
494
|
return answer;
|
|
@@ -501,67 +501,67 @@ async function handleThesisStep(config) {
|
|
|
501
501
|
const theses = data.theses || [];
|
|
502
502
|
const activeTheses = theses.filter((t) => t.status === 'active');
|
|
503
503
|
if (activeTheses.length > 0) {
|
|
504
|
-
console.log(` ${bold('
|
|
504
|
+
console.log(` ${bold('Step 6: Theses')}`);
|
|
505
505
|
blank();
|
|
506
|
-
ok(
|
|
506
|
+
ok(`Found ${activeTheses.length} active thesis(es):`);
|
|
507
507
|
for (const t of activeTheses.slice(0, 5)) {
|
|
508
508
|
const conf = typeof t.confidence === 'number' ? Math.round(t.confidence * 100) : 0;
|
|
509
509
|
const thesis = (t.rawThesis || t.thesis || t.title || '').slice(0, 60);
|
|
510
510
|
info(` ${dim(t.id.slice(0, 8))} — ${thesis} — ${conf}%`);
|
|
511
511
|
}
|
|
512
|
-
info(dim('
|
|
512
|
+
info(dim('Skipping creation.'));
|
|
513
513
|
blank();
|
|
514
514
|
// Offer to launch agent
|
|
515
515
|
if (config.openrouterKey) {
|
|
516
516
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
517
|
-
console.log(` ${bold('
|
|
517
|
+
console.log(` ${bold('All set!')}`);
|
|
518
518
|
blank();
|
|
519
|
-
info(` ${cyan('sf agent')}
|
|
520
|
-
info(` ${cyan('sf context <id>')}
|
|
521
|
-
info(` ${cyan('sf positions')}
|
|
522
|
-
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`);
|
|
523
523
|
blank();
|
|
524
|
-
const shouldLaunch = await promptYN(`
|
|
524
|
+
const shouldLaunch = await promptYN(` Launch agent now? (Y/n) `);
|
|
525
525
|
if (shouldLaunch) {
|
|
526
526
|
blank();
|
|
527
|
-
info('
|
|
527
|
+
info('Launching...');
|
|
528
528
|
blank();
|
|
529
529
|
await (0, agent_js_1.agentCommand)(activeTheses[0].id, { model: config.model });
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
532
|
else {
|
|
533
533
|
blank();
|
|
534
|
-
console.log(` ${bold('
|
|
534
|
+
console.log(` ${bold('All set!')}`);
|
|
535
535
|
blank();
|
|
536
|
-
info(` ${cyan('sf list')}
|
|
537
|
-
info(` ${cyan('sf context <id>')}
|
|
538
|
-
info(` ${cyan('sf positions')}
|
|
539
|
-
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`);
|
|
540
540
|
blank();
|
|
541
541
|
}
|
|
542
542
|
return;
|
|
543
543
|
}
|
|
544
544
|
// No theses — offer to create one
|
|
545
|
-
console.log(` ${bold('
|
|
545
|
+
console.log(` ${bold('Step 6: Create Your First Thesis')}`);
|
|
546
546
|
blank();
|
|
547
|
-
info('
|
|
548
|
-
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.');
|
|
549
549
|
blank();
|
|
550
|
-
info('
|
|
551
|
-
info(` ${dim('"
|
|
552
|
-
info(` ${dim('"AI
|
|
553
|
-
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"')}`);
|
|
554
554
|
blank();
|
|
555
|
-
const thesis = await prompt('
|
|
555
|
+
const thesis = await prompt(' Enter your thesis (press Enter to skip, use sf create later):\n > ');
|
|
556
556
|
if (!thesis) {
|
|
557
557
|
blank();
|
|
558
|
-
info(dim('
|
|
558
|
+
info(dim('Skipped. Use sf create "your thesis" to create one later.'));
|
|
559
559
|
blank();
|
|
560
560
|
showFinalHints(config);
|
|
561
561
|
return;
|
|
562
562
|
}
|
|
563
563
|
blank();
|
|
564
|
-
info('
|
|
564
|
+
info('Building causal model... (~30s)');
|
|
565
565
|
blank();
|
|
566
566
|
try {
|
|
567
567
|
const result = await client.createThesis(thesis, true);
|
|
@@ -570,20 +570,20 @@ async function handleThesisStep(config) {
|
|
|
570
570
|
const edgeCount = result.edgeAnalysis?.edges?.length || 0;
|
|
571
571
|
const totalMarkets = result.edgeAnalysis?.totalMarketsAnalyzed || 0;
|
|
572
572
|
const confidence = Math.round((parseFloat(result.confidence) || 0.5) * 100);
|
|
573
|
-
ok(
|
|
574
|
-
ok(
|
|
575
|
-
ok(
|
|
576
|
-
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)}`);
|
|
577
577
|
blank();
|
|
578
578
|
// Offer to launch agent
|
|
579
579
|
if (config.openrouterKey) {
|
|
580
580
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
581
|
-
console.log(` ${bold('
|
|
581
|
+
console.log(` ${bold('All set!')}`);
|
|
582
582
|
blank();
|
|
583
|
-
const shouldLaunch = await promptYN(`
|
|
583
|
+
const shouldLaunch = await promptYN(` Launch agent now? (Y/n) `);
|
|
584
584
|
if (shouldLaunch) {
|
|
585
585
|
blank();
|
|
586
|
-
info('
|
|
586
|
+
info('Launching...');
|
|
587
587
|
blank();
|
|
588
588
|
await (0, agent_js_1.agentCommand)(result.id, { model: config.model });
|
|
589
589
|
}
|
|
@@ -597,15 +597,15 @@ async function handleThesisStep(config) {
|
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
599
|
else {
|
|
600
|
-
fail(
|
|
601
|
-
info(dim('
|
|
600
|
+
fail(`Creation failed: ${result.error || 'unknown error'}`);
|
|
601
|
+
info(dim('You can retry later with sf create "your thesis"'));
|
|
602
602
|
blank();
|
|
603
603
|
showFinalHints(config);
|
|
604
604
|
}
|
|
605
605
|
}
|
|
606
606
|
catch (err) {
|
|
607
|
-
fail(
|
|
608
|
-
info(dim('
|
|
607
|
+
fail(`Creation failed: ${err.message}`);
|
|
608
|
+
info(dim('You can retry later with sf create "your thesis"'));
|
|
609
609
|
blank();
|
|
610
610
|
showFinalHints(config);
|
|
611
611
|
}
|
|
@@ -618,12 +618,12 @@ async function handleThesisStep(config) {
|
|
|
618
618
|
}
|
|
619
619
|
function showFinalHints(config) {
|
|
620
620
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
621
|
-
console.log(` ${bold('
|
|
621
|
+
console.log(` ${bold('All set!')}`);
|
|
622
622
|
blank();
|
|
623
|
-
info(` ${cyan('sf agent')}
|
|
624
|
-
info(` ${cyan('sf list')}
|
|
625
|
-
info(` ${cyan('sf context <id>')}
|
|
626
|
-
info(` ${cyan('sf positions')}
|
|
627
|
-
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`);
|
|
628
628
|
blank();
|
|
629
629
|
}
|
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
|
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
|
}
|