@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.
@@ -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
@@ -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 Key 有效连接到 ${apiUrl.replace('https://', '')}` };
74
+ return { valid: true, msg: `API key validconnected to ${apiUrl.replace('https://', '')}` };
75
75
  if (res.status === 401)
76
- return { valid: false, msg: '无效 key,请重试' };
77
- return { valid: false, msg: `服务器返回 ${res.status}` };
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: `连接失败: ${err.message}` };
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 连接正常可用模型: claude-sonnet-4.6' };
90
- return { valid: false, msg: `OpenRouter 返回 ${res.status}` };
89
+ return { valid: true, msg: 'OpenRouter connectedavailable 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: `连接失败: ${err.message}` };
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 认证失败', posCount: 0 };
101
- return { valid: true, msg: `Kalshi 认证成功发现 ${positions.length} 个持仓`, posCount: positions.length };
100
+ return { valid: false, msg: 'Kalshi authentication failed', posCount: 0 };
101
+ return { valid: true, msg: `Kalshi authenticatedfound ${positions.length} position(s)`, posCount: positions.length };
102
102
  }
103
103
  catch (err) {
104
- return { valid: false, msg: `Kalshi 连接失败: ${err.message}`, posCount: 0 };
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 返回 ${res.status}` };
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: `连接失败: ${err.message}` };
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('运行 sf setup 重新配置');
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(`保存到 ${(0, config_js_1.getConfigPath)()}`);
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('重新配置 Kalshi 凭证')}`);
154
+ console.log(` ${bold('Reconfigure Kalshi Credentials')}`);
155
155
  blank();
156
- info(' https://kalshi.com/account/api-keys 生成新的 API key');
157
- info('如果需要交易功能,确保勾选 read+write 权限。');
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 未配置(agent 不可用)`);
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('未启用 — sf setup --enable-trading')}`);
226
+ info(`${dim('○')} TRADING ${dim('disabled — sf setup --enable-trading')}`);
227
227
  }
228
228
  blank();
229
- console.log(` ${dim('配置文件: ' + (0, config_js_1.getConfigPath)())}`);
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(' 1 步:API Key')}`);
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(`已检测到 SF_API_KEY — ${dim(mask(existingSfKey))}`);
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(`已有 key 无效: ${result.msg}`);
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(' 2 步:AI 模型(用于 sf agent')}`);
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(`已检测到 OPENROUTER_API_KEY — ${dim(mask(existingOrKey))}`);
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(`已有 key 无效: ${result.msg}`);
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(' 3 步:Kalshi 交易所(可选)')}`);
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(`已检测到 Kalshi — ${dim(mask(existingKalshiId))} (${result.posCount} 个持仓)`);
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(`已有凭证无效: ${result.msg}`);
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(' 4 步:新闻搜索(可选)')}`);
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(`已检测到 TAVILY_API_KEY — ${dim(mask(existingTavily))}`);
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(`已有 key 无效: ${result.msg}`);
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(' 5 步:交易功能(可选)')}`);
349
+ console.log(` ${bold('Step 5: Trading (optional)')}`);
350
350
  blank();
351
- info('⚠️ 启用后 sf buy / sf sell / sf cancel 可用。');
352
- info('你的 Kalshi API key 必须有 read+write 权限。');
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(' 启用交易功能?(y/N) ', false);
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('跳过。之后可以 sf setup --enable-trading 启用。'));
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(`配置保存到 ${dim((0, config_js_1.getConfigPath)())}`);
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(`还没有 key?去 ${cyan('https://simplefunctions.dev/dashboard')} 注册获取。`);
398
- info(' Enter 打开浏览器,或直接粘贴你的 key');
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('浏览器已打开。获取 key 后粘贴到这里:'));
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(`需要 OpenRouter API key。去 ${cyan('https://openrouter.ai/settings/keys')} 获取。`);
422
- info(' Enter 跳过(agent 功能不可用),或粘贴 key');
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('已保存,之后可以重新运行 sf setup 修正。'));
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(`连接 Kalshi 查看你的持仓和盈亏。`);
444
- info(`需要 API Key ID 和私钥文件。`);
445
- info(`${cyan('https://kalshi.com/account/api-keys')} 获取。`);
446
- info(' Enter 跳过,或粘贴 Key ID');
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('私钥文件路径(默认 ~/.kalshi/private.pem):');
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('已保存,之后可以重新运行 sf setup 修正。'));
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 用于 agent web_search 功能。`);
475
- info(`${cyan('https://tavily.com')} 获取免费 key。`);
476
- info(' Enter 跳过:');
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('已保存,之后可以重新运行 sf setup 修正。'));
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(' 6 步:论文')}`);
504
+ console.log(` ${bold('Step 6: Theses')}`);
505
505
  blank();
506
- ok(`已有 ${activeTheses.length} 个活跃论文:`);
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(` 要不要现在启动 agent(Y/n) `);
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(' 6 步:创建你的第一个论文')}`);
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('"美联储2026年不会降息,通胀因油价持续高企"')}`);
552
- info(` ${dim('"AI裁员潮导致消费萎缩,标普年底跌20%"')}`);
553
- info(` ${dim('"Trump无法退出伊朗战争,油价维持$100以上六个月"')}`);
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(' 输入你的论文(按 Enter 跳过,之后用 sf create):\n > ');
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('跳过。之后用 sf create "你的论文" 创建。'));
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('构建因果模型中...(约30秒)');
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(`因果树:${nodeCount} 个节点`);
574
- ok(`扫描 ${totalMarkets} 个市场,找到 ${edgeCount} 个有边际的合约`);
575
- ok(`置信度:${confidence}%`);
576
- ok(`论文 ID:${result.id.slice(0, 8)}`);
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(` 要不要现在启动 agent(Y/n) `);
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(`创建失败:${result.error || '未知错误'}`);
601
- info(dim('之后可以用 sf create "你的论文" 重试'));
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(`创建失败:${err.message}`);
608
- info(dim('之后可以用 sf create "你的论文" 重试'));
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 = thisCommand.name();
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.2.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/simplefunctions/simplefunctions"
42
+ "url": "https://github.com/spfunctions/simplefunctions-cli"
43
43
  }
44
44
  }