@spfunctions/cli 1.5.0 → 1.5.2

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Prediction market intelligence CLI. Build causal thesis models, scan Kalshi/Polymarket for mispricings, detect edges, and trade — all from the terminal.
4
4
 
5
+ ![demo](https://raw.githubusercontent.com/spfunctions/simplefunctions-cli/main/demo.gif)
6
+
5
7
  ## Quick Start
6
8
 
7
9
  ```bash
package/dist/client.js CHANGED
@@ -37,8 +37,17 @@ class SFClient {
37
37
  body: body ? JSON.stringify(body) : undefined,
38
38
  });
39
39
  if (!res.ok) {
40
- const text = await res.text();
41
- throw new Error(`API error ${res.status}: ${text}`);
40
+ let errorBody = null;
41
+ try {
42
+ const text = await res.text();
43
+ errorBody = text ? JSON.parse(text) : null;
44
+ }
45
+ catch { /* not JSON */ }
46
+ const err = new Error(errorBody?.error || errorBody?.message || `API error ${res.status}`);
47
+ err.status = res.status;
48
+ err.code = errorBody?.code || `HTTP_${res.status}`;
49
+ err.details = errorBody;
50
+ throw err;
42
51
  }
43
52
  return res.json();
44
53
  }
@@ -66,7 +66,7 @@ vitest_1.vi.stubGlobal('fetch', mockFetch);
66
66
  status: 404,
67
67
  text: () => Promise.resolve('Not found'),
68
68
  });
69
- await (0, vitest_1.expect)(client.getThesis('bad-id')).rejects.toThrow('API error 404: Not found');
69
+ await (0, vitest_1.expect)(client.getThesis('bad-id')).rejects.toThrow('API error 404');
70
70
  });
71
71
  (0, vitest_1.it)('getContext calls correct path', async () => {
72
72
  mockFetch.mockResolvedValue({
@@ -3180,22 +3180,34 @@ async function runPlainTextAgent(params) {
3180
3180
  .map((n) => ` ${n.id} ${(n.label || '').slice(0, 40)} \u2014 ${Math.round(n.probability * 100)}%`)
3181
3181
  .join('\n') || ' (no causal tree)';
3182
3182
  const conf = typeof ctx.confidence === 'number' ? Math.round(ctx.confidence * 100) : 0;
3183
- const systemPrompt = `You are a prediction market trading assistant. Help the user make correct trading decisions.
3183
+ const systemPrompt = `You are a prediction market trading assistant. Your job is not to please the user — it is to help them see reality clearly and make correct trading decisions.
3184
3184
 
3185
- Current thesis: ${ctx.thesis || ctx.rawThesis || 'N/A'}
3186
- ID: ${resolvedThesisId}
3187
- Confidence: ${conf}%
3188
- Status: ${ctx.status}
3185
+ ## Framework
3186
+ Each thesis has a causal tree. Every node is a hypothesis with a probability. Edge = thesis-implied price - market price. Positive edge = market underprices. Contracts with large edge + good liquidity = most tradeable.
3187
+ executableEdge = edge after subtracting bid-ask spread. A big theoretical edge with wide spread may not be worth entering.
3188
+
3189
+ ## Rules
3190
+ - Be concise. Use tools for fresh data. Don't guess prices.
3191
+ - If user mentions news, inject_signal immediately.
3192
+ - If user says "evaluate", trigger immediately. Don't confirm.
3193
+ - Don't end with "anything else?"
3194
+ - If an edge is narrowing or disappearing, say so proactively.
3195
+ - Use Chinese if user writes Chinese, English if English.
3196
+ - Prices in cents (¢). P&L in dollars ($). Don't re-convert tool output.
3197
+ - When a trade idea emerges, create_strategy to record it.
3198
+ ${config.tradingEnabled ? '- Trading ENABLED. You have place_order and cancel_order tools.' : '- Trading DISABLED. Tell user: sf setup --enable-trading'}
3189
3199
 
3190
- Causal tree nodes:
3200
+ ## Current State
3201
+ Thesis: ${ctx.thesis || ctx.rawThesis || 'N/A'}
3202
+ ID: ${resolvedThesisId} | Confidence: ${conf}% | Status: ${ctx.status}
3203
+
3204
+ Causal nodes:
3191
3205
  ${nodesSummary}
3192
3206
 
3193
3207
  Top edges:
3194
3208
  ${edgesSummary}
3195
3209
 
3196
- ${ctx.lastEvaluation?.summary ? `Latest evaluation: ${ctx.lastEvaluation.summary.slice(0, 300)}` : ''}
3197
-
3198
- Rules: Be concise. Use tools when needed. Don't ask "anything else?". Prices are in cents (e.g. 35¢). P&L, cost, and balance are in dollars (e.g. $90.66). Tool outputs are pre-formatted with units — do not re-convert.`;
3210
+ ${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice(0, 300)}` : ''}`;
3199
3211
  // ── Create agent ──────────────────────────────────────────────────────────
3200
3212
  const agent = new Agent({
3201
3213
  initialState: { systemPrompt, model, tools, thinkingLevel: 'off' },
@@ -1,5 +1,7 @@
1
1
  export declare function createCommand(thesis: string, opts: {
2
2
  async?: boolean;
3
+ json?: boolean;
4
+ timeout?: number;
3
5
  apiKey?: string;
4
6
  apiUrl?: string;
5
7
  }): Promise<void>;
@@ -6,15 +6,27 @@ const utils_js_1 = require("../utils.js");
6
6
  async function createCommand(thesis, opts) {
7
7
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
8
8
  const sync = !opts.async;
9
- if (sync) {
10
- console.log(`${utils_js_1.c.dim}Creating thesis (sync mode — waiting for formation)...${utils_js_1.c.reset}`);
11
- }
12
- else {
13
- console.log(`${utils_js_1.c.dim}Creating thesis (async mode)...${utils_js_1.c.reset}`);
9
+ if (!opts.json) {
10
+ if (sync) {
11
+ console.log(`${utils_js_1.c.dim}Creating thesis (sync mode — waiting for formation)...${utils_js_1.c.reset}`);
12
+ }
13
+ else {
14
+ console.log(`${utils_js_1.c.dim}Creating thesis (async mode)...${utils_js_1.c.reset}`);
15
+ }
14
16
  }
15
17
  const result = await client.createThesis(thesis, sync);
18
+ const id = result.thesis?.id || result.thesisId || result.id || null;
19
+ if (opts.json) {
20
+ console.log(JSON.stringify({ id, status: result.thesis?.status || result.status || 'forming', result }, null, 2));
21
+ return;
22
+ }
23
+ if (!id) {
24
+ console.error(`${utils_js_1.c.red}✗${utils_js_1.c.reset} Thesis creation returned no ID.`);
25
+ console.error(`${utils_js_1.c.dim}Response: ${JSON.stringify(result).slice(0, 200)}${utils_js_1.c.reset}`);
26
+ process.exit(1);
27
+ }
16
28
  console.log(`\n${utils_js_1.c.green}✓${utils_js_1.c.reset} Thesis created`);
17
- console.log(` ${utils_js_1.c.bold}ID:${utils_js_1.c.reset} ${result.thesis?.id || result.id}`);
29
+ console.log(` ${utils_js_1.c.bold}ID:${utils_js_1.c.reset} ${id}`);
18
30
  console.log(` ${utils_js_1.c.bold}Status:${utils_js_1.c.reset} ${result.thesis?.status || result.status}`);
19
31
  if (result.thesis?.confidence) {
20
32
  console.log(` ${utils_js_1.c.bold}Confidence:${utils_js_1.c.reset} ${Math.round(parseFloat(result.thesis.confidence) * 100)}%`);
@@ -25,7 +37,6 @@ async function createCommand(thesis, opts) {
25
37
  if (result.thesis?.edgeAnalysis?.edges) {
26
38
  console.log(` ${utils_js_1.c.bold}Edges:${utils_js_1.c.reset} ${result.thesis.edgeAnalysis.edges.length}`);
27
39
  }
28
- const id = result.thesis?.id || result.id;
29
40
  console.log(`\n${utils_js_1.c.dim}View: sf get ${(0, utils_js_1.shortId)(id)}${utils_js_1.c.reset}`);
30
41
  console.log(`${utils_js_1.c.dim}Context: sf context ${(0, utils_js_1.shortId)(id)}${utils_js_1.c.reset}`);
31
42
  }
@@ -1,4 +1,5 @@
1
1
  export declare function listCommand(opts: {
2
+ json?: boolean;
2
3
  apiKey?: string;
3
4
  apiUrl?: string;
4
5
  }): Promise<void>;
@@ -6,6 +6,10 @@ const utils_js_1 = require("../utils.js");
6
6
  async function listCommand(opts) {
7
7
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
8
8
  const { theses } = await client.listTheses();
9
+ if (opts.json) {
10
+ console.log(JSON.stringify(theses, null, 2));
11
+ return;
12
+ }
9
13
  if (theses.length === 0) {
10
14
  console.log(`${utils_js_1.c.dim}No theses found.${utils_js_1.c.reset}`);
11
15
  return;
package/dist/index.js CHANGED
@@ -165,9 +165,10 @@ program
165
165
  program
166
166
  .command('list')
167
167
  .description('List all theses')
168
- .action(async (_opts, cmd) => {
168
+ .option('--json', 'JSON output')
169
+ .action(async (opts, cmd) => {
169
170
  const g = cmd.optsWithGlobals();
170
- await run(() => (0, list_js_1.listCommand)({ apiKey: g.apiKey, apiUrl: g.apiUrl }));
171
+ await run(() => (0, list_js_1.listCommand)({ json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
171
172
  });
172
173
  // ── sf get <id> ───────────────────────────────────────────────────────────────
173
174
  program
@@ -192,9 +193,10 @@ program
192
193
  .command('create <thesis>')
193
194
  .description('Create a new thesis (sync by default — waits for formation)')
194
195
  .option('--async', 'Async mode — return immediately without waiting')
196
+ .option('--json', 'JSON output')
195
197
  .action(async (thesis, opts, cmd) => {
196
198
  const g = cmd.optsWithGlobals();
197
- await run(() => (0, create_js_1.createCommand)(thesis, { async: opts.async, apiKey: g.apiKey, apiUrl: g.apiUrl }));
199
+ await run(() => (0, create_js_1.createCommand)(thesis, { async: opts.async, json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
198
200
  });
199
201
  // ── sf signal <id> <content> ──────────────────────────────────────────────────
200
202
  program
@@ -516,6 +518,15 @@ async function run(fn) {
516
518
  }
517
519
  catch (err) {
518
520
  const msg = err instanceof Error ? err.message : String(err);
521
+ // If --json flag is present, output structured JSON error
522
+ if (process.argv.includes('--json')) {
523
+ console.log(JSON.stringify({
524
+ error: msg,
525
+ code: err.code || 'CLI_ERROR',
526
+ status: err.status || 1,
527
+ }));
528
+ process.exit(1);
529
+ }
519
530
  (0, utils_js_1.die)(msg);
520
531
  }
521
532
  }
@@ -274,6 +274,53 @@ async function buildTools(sfClient, thesisId, latestContext) {
274
274
  },
275
275
  },
276
276
  ];
277
+ // Trading tools (only if enabled)
278
+ if (config.tradingEnabled) {
279
+ tools.push({
280
+ name: 'place_order',
281
+ label: 'Place Order',
282
+ description: 'Place a buy or sell order on Kalshi. Requires trading to be enabled. Always confirm with user before placing.',
283
+ parameters: Type.Object({
284
+ ticker: Type.String({ description: 'Market ticker (e.g. KXWTIMAX-26DEC31-T135)' }),
285
+ side: Type.String({ description: 'yes or no' }),
286
+ action: Type.String({ description: 'buy or sell' }),
287
+ count: Type.Number({ description: 'Number of contracts' }),
288
+ yes_price: Type.Number({ description: 'Price in cents (1-99)' }),
289
+ }),
290
+ execute: async (_id, p) => {
291
+ try {
292
+ const result = await kalshi.createOrder({
293
+ ticker: p.ticker,
294
+ side: p.side,
295
+ action: p.action,
296
+ type: 'limit',
297
+ count: p.count,
298
+ yes_price: p.yes_price,
299
+ });
300
+ return { content: [{ type: 'text', text: `✓ Order placed: ${(result.order || result).order_id || 'OK'}\n${p.action.toUpperCase()} ${p.count}x ${p.ticker} ${p.side.toUpperCase()} @ ${p.yes_price}¢` }], details: {} };
301
+ }
302
+ catch (err) {
303
+ return { content: [{ type: 'text', text: `✗ Order failed: ${err.message}` }], details: {} };
304
+ }
305
+ },
306
+ }, {
307
+ name: 'cancel_order',
308
+ label: 'Cancel Order',
309
+ description: 'Cancel a resting order on Kalshi.',
310
+ parameters: Type.Object({
311
+ orderId: Type.String({ description: 'Order ID to cancel' }),
312
+ }),
313
+ execute: async (_id, p) => {
314
+ try {
315
+ await kalshi.cancelOrder(p.orderId);
316
+ return { content: [{ type: 'text', text: `✓ Order ${p.orderId} cancelled.` }], details: {} };
317
+ }
318
+ catch (err) {
319
+ return { content: [{ type: 'text', text: `✗ Cancel failed: ${err.message}` }], details: {} };
320
+ }
321
+ },
322
+ });
323
+ }
277
324
  return tools;
278
325
  }
279
326
  async function getOrCreateAgent(sfClient, session) {
@@ -303,17 +350,43 @@ async function getOrCreateAgent(sfClient, session) {
303
350
  supportsImages: true, supportsTools: true,
304
351
  };
305
352
  }
306
- const systemPrompt = `You are a prediction market trading assistant on Telegram. Be concise.
353
+ const edgesSummary = (ctx.edges || [])
354
+ .sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
355
+ .slice(0, 5)
356
+ .map((e) => ` ${(e.market || '').slice(0, 35)} | ${e.venue || 'kalshi'} | mkt ${e.marketPrice}¢ → thesis ${e.thesisPrice}¢ | edge ${e.edge > 0 ? '+' : ''}${e.edge}`)
357
+ .join('\n') || ' (no edges)';
358
+ const nodesSummary = (ctx.causalTree?.nodes || [])
359
+ .filter((n) => n.depth === 0 || !n.depth)
360
+ .slice(0, 5)
361
+ .map((n) => ` ${n.id} ${(n.label || '').slice(0, 35)} — ${Math.round((n.probability || 0.5) * 100)}%`)
362
+ .join('\n') || ' (no causal tree)';
363
+ const systemPrompt = `You are a prediction market trading assistant on Telegram. Your job is to help the user see reality clearly and make correct trading decisions.
307
364
 
308
- Thesis: ${(ctx.thesis || ctx.rawThesis || 'N/A').slice(0, 200)}
309
- ID: ${session.thesisId.slice(0, 8)}
310
- Confidence: ${conf}%
365
+ ## Framework
366
+ Edge = thesis price - market price. Positive = market underprices. Negative = overpriced.
367
+ Contracts with large edge + good liquidity = most tradeable.
311
368
 
312
- Rules:
313
- - Keep responses shortTelegram messages should be brief.
314
- - Prices are in cents (¢). P&L in dollars ($). Tool outputs are pre-formatted.
369
+ ## Rules
370
+ - Keep Telegram messages SHORT bullet points, no walls of text.
371
+ - Prices in cents (¢). P&L in dollars ($). Don't re-convert tool output units.
372
+ - Call tools for fresh data. Never guess prices or P&L from this prompt.
373
+ - If user mentions news, inject_signal immediately. Don't ask "should I?"
374
+ - If user says "evaluate" or "run it", trigger immediately.
375
+ - Don't end with "anything else?" — user will ask.
315
376
  - Use Chinese if user writes Chinese, English if English.
316
- - Call tools when needed. Don't guess data.`;
377
+ ${config.tradingEnabled ? '- Trading ENABLED. You have place_order and cancel_order. ALWAYS confirm before placing.' : '- Trading DISABLED. Tell user: sf setup --enable-trading'}
378
+
379
+ ## Current State
380
+ Thesis: ${(ctx.thesis || ctx.rawThesis || 'N/A').slice(0, 200)}
381
+ ID: ${session.thesisId.slice(0, 8)} | Confidence: ${conf}%
382
+
383
+ Causal nodes:
384
+ ${nodesSummary}
385
+
386
+ Top edges:
387
+ ${edgesSummary}
388
+
389
+ ${ctx.lastEvaluation?.summary ? `Latest eval: ${ctx.lastEvaluation.summary.slice(0, 200)}` : ''}`;
317
390
  const agent = new Agent({
318
391
  initialState: {
319
392
  systemPrompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
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"