@spfunctions/cli 1.5.3 → 1.7.1

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/client.d.ts CHANGED
@@ -22,6 +22,7 @@ export declare class SFClient {
22
22
  updateThesis(id: string, data: Record<string, unknown>): Promise<any>;
23
23
  publish(id: string, slug: string, description?: string): Promise<any>;
24
24
  unpublish(id: string): Promise<any>;
25
+ augmentTree(id: string, dryRun?: boolean): Promise<any>;
25
26
  getStrategies(id: string, status?: string): Promise<any>;
26
27
  createStrategyAPI(id: string, data: Record<string, unknown>): Promise<any>;
27
28
  updateStrategyAPI(thesisId: string, strategyId: string, data: Record<string, unknown>): Promise<any>;
@@ -32,3 +33,5 @@ export declare function kalshiFetchEvents(seriesTicker: string): Promise<any[]>;
32
33
  export declare function kalshiFetchMarket(ticker: string): Promise<any>;
33
34
  export declare function kalshiFetchMarketsBySeries(seriesTicker: string): Promise<any[]>;
34
35
  export declare function kalshiFetchMarketsByEvent(eventTicker: string): Promise<any[]>;
36
+ export declare function fetchGlobalContext(): Promise<any>;
37
+ export declare function fetchQuery(q: string): Promise<any>;
package/dist/client.js CHANGED
@@ -12,6 +12,8 @@ exports.kalshiFetchEvents = kalshiFetchEvents;
12
12
  exports.kalshiFetchMarket = kalshiFetchMarket;
13
13
  exports.kalshiFetchMarketsBySeries = kalshiFetchMarketsBySeries;
14
14
  exports.kalshiFetchMarketsByEvent = kalshiFetchMarketsByEvent;
15
+ exports.fetchGlobalContext = fetchGlobalContext;
16
+ exports.fetchQuery = fetchQuery;
15
17
  const DEFAULT_API_URL = 'https://simplefunctions.dev';
16
18
  const KALSHI_BASE = 'https://api.elections.kalshi.com/trade-api/v2';
17
19
  // ===== SF API Client =====
@@ -86,6 +88,10 @@ class SFClient {
86
88
  async unpublish(id) {
87
89
  return this.request('DELETE', `/api/thesis/${id}/publish`);
88
90
  }
91
+ async augmentTree(id, dryRun = false) {
92
+ const qs = dryRun ? '?dryRun=true' : '';
93
+ return this.request('POST', `/api/thesis/${id}/augment${qs}`);
94
+ }
89
95
  // ── Strategy operations ──
90
96
  async getStrategies(id, status) {
91
97
  const qs = status ? `?status=${status}` : '';
@@ -150,3 +156,17 @@ async function kalshiFetchMarketsByEvent(eventTicker) {
150
156
  });
151
157
  return data.markets || [];
152
158
  }
159
+ // ===== Public Endpoints (no auth) =====
160
+ const SF_PUBLIC_BASE = 'https://simplefunctions.dev';
161
+ async function fetchGlobalContext() {
162
+ const res = await fetch(`${SF_PUBLIC_BASE}/api/public/context`);
163
+ if (!res.ok)
164
+ throw new Error(`Context API error: ${res.status}`);
165
+ return res.json();
166
+ }
167
+ async function fetchQuery(q) {
168
+ const res = await fetch(`${SF_PUBLIC_BASE}/api/public/query?q=${encodeURIComponent(q)}`);
169
+ if (!res.ok)
170
+ throw new Error(`Query API error: ${res.status}`);
171
+ return res.json();
172
+ }
@@ -746,6 +746,30 @@ async function agentCommand(thesisId, opts) {
746
746
  };
747
747
  },
748
748
  },
749
+ {
750
+ name: 'global_context',
751
+ label: 'Market Snapshot',
752
+ description: 'Global market snapshot — top movers, expiring contracts, milestones, liquidity, signals. No thesis needed.',
753
+ parameters: emptyParams,
754
+ execute: async () => {
755
+ const { fetchGlobalContext } = await import('../client.js');
756
+ const data = await fetchGlobalContext();
757
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
758
+ },
759
+ },
760
+ {
761
+ name: 'query',
762
+ label: 'Query',
763
+ description: 'LLM-enhanced prediction market knowledge search. Ask any question about prediction markets, macro, geopolitics. Returns structured answer with live market prices, thesis data, and key factors.',
764
+ parameters: Type.Object({
765
+ q: Type.String({ description: 'Natural language query (e.g. "iran oil prices", "fed rate cut 2026")' }),
766
+ }),
767
+ execute: async (_toolCallId, params) => {
768
+ const { fetchQuery } = await import('../client.js');
769
+ const data = await fetchQuery(params.q);
770
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
771
+ },
772
+ },
749
773
  {
750
774
  name: 'inject_signal',
751
775
  label: 'Inject Signal',
@@ -1570,16 +1594,48 @@ async function agentCommand(thesisId, opts) {
1570
1594
 
1571
1595
  Each thesis has a causal tree. Every node is a causal hypothesis with a probability. Nodes have causal relationships \u2014 when upstream nodes change, downstream nodes follow.
1572
1596
 
1573
- Edge = thesis-implied price - actual market price. Positive edge means the market underprices this event. Negative edge means overpriced. Contracts with large edges AND good liquidity are the most tradeable.
1597
+ Edge = thesis-implied price - actual market price. Positive edge = market underprices. Contracts with large edges AND good liquidity are most tradeable.
1598
+
1599
+ executableEdge = real edge after subtracting bid-ask spread. Big theoretical edge with wide spread may not be worth entering.
1600
+
1601
+ ### Edge diagnosis (always classify)
1602
+ When reporting an edge, classify it:
1603
+ - "consensus gap": depth >= 500, market actively disagrees — real edge, real opponents
1604
+ - "attention gap": depth < 100, no real pricing — edge may be illusory, needs catalyst
1605
+ - "timing gap": market directionally agrees but lags — may close suddenly on news
1606
+ - "risk premium": edge reflects settlement ambiguity or platform risk, not alpha
1607
+ For edges > 20 cents, state in one sentence what must be true for the market to be right and the thesis wrong.
1608
+
1609
+ ### Price reliability
1610
+ 47 cents with depth 14K = strong consensus. 47 cents with depth 50 = three people's opinion.
1611
+ - depth >= 500: reliable, treat as market consensus
1612
+ - depth 100-500: moderate confidence, caveat when citing
1613
+ - depth < 100: unreliable — do NOT treat as "the market thinks X"
1614
+ - spread > 5 cents: wide disagreement, noisy midpoint
1615
+ - liquidityScore = low: do NOT recommend entry
1616
+
1617
+ ### Kill condition awareness
1618
+ Each top-level causal node has an implicit falsifier. When scanning news or evaluating events, check: "Does any event here fundamentally break a core assumption?" If yes, flag immediately — this overrides all other analysis.
1619
+
1620
+ ### Catalyst and time
1621
+ When discussing an edge, always state contract expiry and next identifiable catalyst. No visible catalyst = flag capital lock risk.
1574
1622
 
1575
- executableEdge is the real edge after subtracting the bid-ask spread. A contract with a big theoretical edge but wide spread may not be worth entering.
1623
+ ### Research workflow
1624
+ For complex questions, chain multiple tool calls:
1625
+ 1. get_context 2. inspect_book 3. get_liquidity 4. web_search 5. synthesize
1626
+ Don't answer a complex question with a single tool call.
1576
1627
 
1577
- Short-term markets (weekly/monthly contracts) settle into hard data that calibrates the long-term thesis. Don't use them to bet (outcomes are nearly known) \u2014 use them to verify whether causal tree node probabilities are accurate.
1628
+ ### Conditional rules
1629
+ - Portfolio/positions questions: flag correlated exposure — positions sharing upstream causal nodes are not independent bets.
1630
+ - No catalyst visible within 30 days + edge not improving: flag "stale capital risk."
1631
+ - Edges < 10 cents, thin liquidity, no catalyst near: say "nothing to do right now." Do not manufacture urgency.
1632
+
1633
+ Short-term markets settle into hard data that calibrates the thesis. Use them to verify node probabilities, not to bet.
1578
1634
 
1579
1635
  ## Your behavioral rules
1580
1636
 
1637
+ - IMPORTANT: You do NOT know the user's current positions at conversation start. Before discussing trades, recommending entries/exits, or analyzing portfolio risk, call get_positions FIRST. Never assume the user has no positions — they may have large existing holdings you don't know about.
1581
1638
  - Think before calling tools. If the data is already in context, don't re-fetch.
1582
- - If the user asks about positions, check if Kalshi is configured first. If not, say so directly.
1583
1639
  - If the user says "note this" or mentions a news event, inject a signal. Don't ask "should I note this?"
1584
1640
  - If the user says "evaluate" or "run it", trigger immediately. Don't confirm.
1585
1641
  - Don't end every response with "anything else?" \u2014 the user will ask when they want to.
@@ -2508,6 +2564,26 @@ async function runPlainTextAgent(params) {
2508
2564
  return { content: [{ type: 'text', text: JSON.stringify(ctx, null, 2) }], details: {} };
2509
2565
  },
2510
2566
  },
2567
+ {
2568
+ name: 'global_context', label: 'Market Snapshot',
2569
+ description: 'Global market snapshot — top movers, expiring contracts, milestones, liquidity, signals. No thesis needed.',
2570
+ parameters: emptyParams,
2571
+ execute: async () => {
2572
+ const { fetchGlobalContext } = await import('../client.js');
2573
+ const data = await fetchGlobalContext();
2574
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
2575
+ },
2576
+ },
2577
+ {
2578
+ name: 'query', label: 'Query',
2579
+ description: 'LLM-enhanced prediction market knowledge search. Returns structured answer with live prices, thesis data, key factors.',
2580
+ parameters: Type.Object({ q: Type.String({ description: 'Natural language query' }) }),
2581
+ execute: async (_id, p) => {
2582
+ const { fetchQuery } = await import('../client.js');
2583
+ const data = await fetchQuery(p.q);
2584
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
2585
+ },
2586
+ },
2511
2587
  {
2512
2588
  name: 'inject_signal', label: 'Inject Signal',
2513
2589
  description: 'Inject a signal into the thesis',
@@ -3183,11 +3259,24 @@ async function runPlainTextAgent(params) {
3183
3259
  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
3260
 
3185
3261
  ## 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.
3262
+ Edge = thesis price - market price. Positive = market underprices. executableEdge = edge minus spread.
3263
+
3264
+ Edge types: "consensus_gap" (depth >= 500, real disagreement), "attention_gap" (depth < 100, illusory pricing), "timing_gap" (market lags), "risk_premium" (settlement/platform risk). Always classify when reporting edges.
3265
+ For edges > 20 cents, state what the market must believe for it to be right.
3266
+
3267
+ Price reliability: depth >= 500 = consensus. depth < 100 = unreliable. spread > 5 cents = noisy. Flag illiquid markets.
3268
+
3269
+ Kill conditions: each causal node has a falsifier. Check these first when evaluating news. If triggered, override other analysis.
3270
+
3271
+ Always state contract expiry and next catalyst. No catalyst = flag capital lock risk.
3272
+
3273
+ For complex questions, chain: get_context -> inspect_book -> get_liquidity -> web_search -> synthesize.
3274
+
3275
+ Flag correlated exposure across positions sharing upstream nodes. If nothing to do, say so.
3188
3276
 
3189
3277
  ## Rules
3190
3278
  - Be concise. Use tools for fresh data. Don't guess prices.
3279
+ - You do NOT know the user's positions at start. Call get_positions before discussing trades or portfolio.
3191
3280
  - If user mentions news, inject_signal immediately.
3192
3281
  - If user says "evaluate", trigger immediately. Don't confirm.
3193
3282
  - Don't end with "anything else?"
@@ -0,0 +1,12 @@
1
+ /**
2
+ * sf augment <thesisId> [--dry-run]
3
+ *
4
+ * Review suggested causal tree nodes from recent evaluations,
5
+ * accept/reject via LLM, and merge into the tree (append-only).
6
+ */
7
+ export declare function augmentCommand(thesisId: string, opts: {
8
+ dryRun?: boolean;
9
+ json?: boolean;
10
+ apiKey?: string;
11
+ apiUrl?: string;
12
+ }): Promise<void>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * sf augment <thesisId> [--dry-run]
4
+ *
5
+ * Review suggested causal tree nodes from recent evaluations,
6
+ * accept/reject via LLM, and merge into the tree (append-only).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.augmentCommand = augmentCommand;
10
+ const client_js_1 = require("../client.js");
11
+ async function augmentCommand(thesisId, opts) {
12
+ const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
13
+ console.log(`\x1b[2m${opts.dryRun ? 'Previewing' : 'Running'} tree augmentation for ${thesisId.slice(0, 8)}...\x1b[22m`);
14
+ const result = await client.augmentTree(thesisId, opts.dryRun);
15
+ if (opts.json) {
16
+ console.log(JSON.stringify(result, null, 2));
17
+ return;
18
+ }
19
+ // Display results
20
+ console.log();
21
+ if (result.warning) {
22
+ console.log(` \x1b[33m⚠ ${result.warning}\x1b[39m`);
23
+ console.log();
24
+ }
25
+ if (result.suggestedNodes?.length > 0) {
26
+ console.log(` \x1b[1mSuggested Nodes\x1b[22m (${result.suggestedNodes.length} from evaluations)`);
27
+ for (const s of result.suggestedNodes) {
28
+ console.log(` • ${s.label} \x1b[2m(parent: ${s.parentNodeId}, est: ${Math.round(s.estimatedProbability * 100)}%, seen ${s.frequency}x)\x1b[22m`);
29
+ }
30
+ console.log();
31
+ }
32
+ if (result.acceptedNodes?.length > 0) {
33
+ console.log(` \x1b[1m\x1b[32mAccepted Nodes\x1b[39m\x1b[22m (${result.acceptedNodes.length})`);
34
+ for (const n of result.acceptedNodes) {
35
+ console.log(` \x1b[32m+\x1b[39m ${n.id}: ${n.label} \x1b[2m(prob: ${Math.round(n.probability * 100)}%, imp: ${n.importance})\x1b[22m`);
36
+ }
37
+ console.log();
38
+ if (Object.keys(result.updatedImportanceWeights || {}).length > 0) {
39
+ console.log(` \x1b[1mRebalanced Weights\x1b[22m`);
40
+ for (const [nodeId, weight] of Object.entries(result.updatedImportanceWeights)) {
41
+ console.log(` ${nodeId} → ${weight}`);
42
+ }
43
+ console.log();
44
+ }
45
+ if (result.applied) {
46
+ console.log(` \x1b[32m✓ Applied to causal tree\x1b[39m`);
47
+ }
48
+ else if (opts.dryRun) {
49
+ console.log(` \x1b[2mDry run — use without --dry-run to apply\x1b[22m`);
50
+ }
51
+ }
52
+ else {
53
+ console.log(' No nodes accepted.');
54
+ }
55
+ console.log();
56
+ }
@@ -1,4 +1,4 @@
1
- export declare function contextCommand(id: string, opts: {
1
+ export declare function contextCommand(id: string | undefined, opts: {
2
2
  json?: boolean;
3
3
  apiKey?: string;
4
4
  apiUrl?: string;
@@ -5,6 +5,63 @@ const client_js_1 = require("../client.js");
5
5
  const kalshi_js_1 = require("../kalshi.js");
6
6
  const utils_js_1 = require("../utils.js");
7
7
  async function contextCommand(id, opts) {
8
+ // No thesis ID → global market snapshot
9
+ if (!id) {
10
+ const res = await fetch('https://simplefunctions.dev/api/public/context');
11
+ if (!res.ok) {
12
+ console.error(` Error: ${res.status} ${await res.text()}`);
13
+ return;
14
+ }
15
+ const data = await res.json();
16
+ if (opts.json) {
17
+ console.log(JSON.stringify(data, null, 2));
18
+ return;
19
+ }
20
+ console.log(`\n${utils_js_1.c.bold}Market Snapshot${utils_js_1.c.reset} ${(0, utils_js_1.shortDate)(data.snapshotAt)}\n`);
21
+ if (data.movers?.length > 0) {
22
+ console.log(`${utils_js_1.c.bold}Movers (24h)${utils_js_1.c.reset}`);
23
+ for (const m of data.movers.slice(0, 10)) {
24
+ const venue = m.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
25
+ const ch = m.change24h > 0 ? `${utils_js_1.c.green}+${m.change24h}¢${utils_js_1.c.reset}` : `${utils_js_1.c.red}${m.change24h}¢${utils_js_1.c.reset}`;
26
+ console.log(` ${venue} ${String(m.price).padStart(3)}¢ ${ch.padStart(16)} ${m.title.slice(0, 55)}`);
27
+ }
28
+ console.log();
29
+ }
30
+ if (data.milestones?.length > 0) {
31
+ console.log(`${utils_js_1.c.bold}Upcoming (48h)${utils_js_1.c.reset}`);
32
+ for (const m of data.milestones.slice(0, 8)) {
33
+ console.log(` ${utils_js_1.c.dim}${String(m.hoursUntil).padStart(3)}h${utils_js_1.c.reset} ${m.title.slice(0, 55)} ${utils_js_1.c.dim}${m.category}${utils_js_1.c.reset}`);
34
+ }
35
+ console.log();
36
+ }
37
+ if (data.expiring?.length > 0) {
38
+ console.log(`${utils_js_1.c.bold}Expiring (7d)${utils_js_1.c.reset}`);
39
+ for (const m of data.expiring.slice(0, 8)) {
40
+ const venue = m.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
41
+ console.log(` ${venue} ${String(m.price).padStart(3)}¢ ${utils_js_1.c.dim}${String(m.hoursUntil).padStart(3)}h${utils_js_1.c.reset} ${m.title.slice(0, 55)}`);
42
+ }
43
+ console.log();
44
+ }
45
+ if (data.liquid?.length > 0) {
46
+ console.log(`${utils_js_1.c.bold}Liquid${utils_js_1.c.reset}`);
47
+ for (const m of data.liquid.slice(0, 8)) {
48
+ const venue = m.venue === 'kalshi' ? `${utils_js_1.c.cyan}K${utils_js_1.c.reset}` : `${utils_js_1.c.magenta}P${utils_js_1.c.reset}`;
49
+ console.log(` ${venue} ${String(m.price).padStart(3)}¢ spread ${m.spread}¢ ${m.title.slice(0, 55)}`);
50
+ }
51
+ console.log();
52
+ }
53
+ if (data.signals?.length > 0) {
54
+ console.log(`${utils_js_1.c.bold}Recent Signals${utils_js_1.c.reset}`);
55
+ for (const s of data.signals.slice(0, 5)) {
56
+ const d = s.confidenceDelta > 0 ? `${utils_js_1.c.green}▲${s.confidenceDelta}${utils_js_1.c.reset}` : s.confidenceDelta < 0 ? `${utils_js_1.c.red}▼${s.confidenceDelta}${utils_js_1.c.reset}` : `${s.confidenceDelta}`;
57
+ console.log(` ${d} ${s.confidence}% ${s.summary.slice(0, 60)}`);
58
+ console.log(` ${utils_js_1.c.dim}${s.thesisTitle.slice(0, 40)} ${(0, utils_js_1.shortDate)(s.evaluatedAt)}${utils_js_1.c.reset}`);
59
+ }
60
+ console.log();
61
+ }
62
+ console.log(` ${utils_js_1.c.dim}Create a thesis for focused context: sf create "your market view"${utils_js_1.c.reset}\n`);
63
+ return;
64
+ }
8
65
  const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
9
66
  const ctx = await client.getContext(id);
10
67
  if (opts.json) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * sf prompt [thesisId] — Get dynamic system prompt for an agent
3
+ *
4
+ * Outputs a ready-to-use system prompt with live thesis state.
5
+ * Pipe-friendly: sf prompt f582bf76 >> agent_context.txt
6
+ */
7
+ export declare function promptCommand(thesisId: string | undefined, opts: {
8
+ sections?: string;
9
+ maxLength?: string;
10
+ json?: boolean;
11
+ apiKey?: string;
12
+ apiUrl?: string;
13
+ }): Promise<void>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * sf prompt [thesisId] — Get dynamic system prompt for an agent
4
+ *
5
+ * Outputs a ready-to-use system prompt with live thesis state.
6
+ * Pipe-friendly: sf prompt f582bf76 >> agent_context.txt
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.promptCommand = promptCommand;
10
+ async function promptCommand(thesisId, opts) {
11
+ const baseUrl = (opts.apiUrl || process.env.SF_API_URL || 'https://simplefunctions.dev').replace(/\/$/, '');
12
+ const apiKey = opts.apiKey || process.env.SF_API_KEY || '';
13
+ const params = new URLSearchParams();
14
+ if (opts.sections)
15
+ params.set('sections', opts.sections);
16
+ if (opts.maxLength)
17
+ params.set('maxLength', opts.maxLength);
18
+ if (opts.json)
19
+ params.set('format', 'json');
20
+ const path = thesisId
21
+ ? `/api/thesis/${thesisId}/prompt`
22
+ : '/api/prompt';
23
+ const qs = params.toString() ? `?${params.toString()}` : '';
24
+ const res = await fetch(`${baseUrl}${path}${qs}`, {
25
+ headers: { 'Authorization': `Bearer ${apiKey}` },
26
+ });
27
+ if (!res.ok) {
28
+ const text = await res.text();
29
+ throw new Error(`API error ${res.status}: ${text}`);
30
+ }
31
+ const body = await res.text();
32
+ process.stdout.write(body);
33
+ if (!body.endsWith('\n'))
34
+ process.stdout.write('\n');
35
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * sf query "iran oil"
3
+ *
4
+ * LLM-enhanced prediction market knowledge search. No auth required.
5
+ * Searches Kalshi, Polymarket, and SimpleFunctions content.
6
+ * Returns structured answer with markets, thesis, content, key factors.
7
+ *
8
+ * Usage:
9
+ * sf query "iran oil" — Formatted output
10
+ * sf query "fed rate cut" --json — JSON for agents
11
+ */
12
+ export declare function queryCommand(q: string, opts?: {
13
+ json?: boolean;
14
+ limit?: string;
15
+ }): Promise<void>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /**
3
+ * sf query "iran oil"
4
+ *
5
+ * LLM-enhanced prediction market knowledge search. No auth required.
6
+ * Searches Kalshi, Polymarket, and SimpleFunctions content.
7
+ * Returns structured answer with markets, thesis, content, key factors.
8
+ *
9
+ * Usage:
10
+ * sf query "iran oil" — Formatted output
11
+ * sf query "fed rate cut" --json — JSON for agents
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.queryCommand = queryCommand;
15
+ const BASE_URL = 'https://simplefunctions.dev';
16
+ async function queryCommand(q, opts) {
17
+ const limit = opts?.limit || '10';
18
+ const url = `${BASE_URL}/api/public/query?q=${encodeURIComponent(q)}&limit=${limit}`;
19
+ const res = await fetch(url);
20
+ if (!res.ok) {
21
+ const text = await res.text();
22
+ if (res.status === 429) {
23
+ console.error(' Rate limited. Wait a minute and try again.');
24
+ }
25
+ else {
26
+ console.error(` Error: ${res.status} ${text}`);
27
+ }
28
+ return;
29
+ }
30
+ const data = await res.json();
31
+ if (opts?.json) {
32
+ console.log(JSON.stringify(data, null, 2));
33
+ return;
34
+ }
35
+ // Formatted output
36
+ console.log();
37
+ console.log(` \x1b[1m${data.query}\x1b[22m`);
38
+ console.log();
39
+ // Answer
40
+ if (data.answer) {
41
+ console.log(` ${data.answer}`);
42
+ console.log();
43
+ }
44
+ // Key factors
45
+ if (data.keyFactors?.length > 0) {
46
+ console.log(' \x1b[2mKey factors:\x1b[22m');
47
+ for (const f of data.keyFactors) {
48
+ console.log(` • ${f}`);
49
+ }
50
+ console.log();
51
+ }
52
+ // Markets
53
+ if (data.markets?.length > 0) {
54
+ console.log(' \x1b[1mMarkets\x1b[22m');
55
+ for (const m of data.markets.slice(0, 8)) {
56
+ const venue = m.venue === 'kalshi' ? '\x1b[36mK\x1b[39m' : '\x1b[35mP\x1b[39m';
57
+ const vol = m.volume > 1_000_000 ? `${(m.volume / 1_000_000).toFixed(1)}M` :
58
+ m.volume > 1_000 ? `${(m.volume / 1_000).toFixed(0)}K` :
59
+ String(m.volume);
60
+ const ticker = m.ticker ? ` ${m.ticker}` : '';
61
+ console.log(` ${venue} ${String(m.price).padStart(3)}¢ vol ${vol.padStart(6)} ${m.title.slice(0, 55)}${ticker}`);
62
+ }
63
+ console.log();
64
+ }
65
+ // Thesis
66
+ if (data.thesis) {
67
+ const t = data.thesis;
68
+ console.log(` \x1b[1mThesis\x1b[22m`);
69
+ console.log(` ${t.title.slice(0, 60)}`);
70
+ console.log(` ${t.confidence}% confidence ${t.edges} edges /thesis/${t.slug}`);
71
+ console.log();
72
+ }
73
+ // Content
74
+ if (data.content?.length > 0) {
75
+ console.log(' \x1b[1mContent\x1b[22m');
76
+ for (const c of data.content.slice(0, 5)) {
77
+ const type = c.type.padEnd(9);
78
+ console.log(` \x1b[2m${type}\x1b[22m ${c.title.slice(0, 60)}`);
79
+ }
80
+ console.log();
81
+ }
82
+ // Sources
83
+ if (data.sources?.length > 0) {
84
+ console.log(` \x1b[2mSources: ${data.sources.join(', ')}\x1b[22m`);
85
+ console.log();
86
+ }
87
+ }
@@ -218,6 +218,11 @@ async function keywordScan(query, json, venue = 'all') {
218
218
  ` ${(m.title || '').slice(0, 55)}`);
219
219
  }
220
220
  console.log('');
221
+ // Conversion hook
222
+ if (!json) {
223
+ console.log(`${utils_js_1.c.dim}Want 24/7 monitoring + edge detection? ${utils_js_1.c.reset}${utils_js_1.c.cyan}sf create${utils_js_1.c.reset}${utils_js_1.c.dim} "your thesis"${utils_js_1.c.reset}`);
224
+ console.log('');
225
+ }
221
226
  }
222
227
  function printMarketsTable(markets) {
223
228
  if (markets.length === 0) {
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
22
  const commander_1 = require("commander");
23
23
  const config_js_1 = require("./config.js");
24
+ const client_js_1 = require("./client.js");
24
25
  const list_js_1 = require("./commands/list.js");
25
26
  const get_js_1 = require("./commands/get.js");
26
27
  const context_js_1 = require("./commands/context.js");
@@ -53,7 +54,10 @@ const history_js_1 = require("./commands/history.js");
53
54
  const performance_js_1 = require("./commands/performance.js");
54
55
  const liquidity_js_1 = require("./commands/liquidity.js");
55
56
  const book_js_1 = require("./commands/book.js");
57
+ const prompt_js_1 = require("./commands/prompt.js");
58
+ const augment_js_1 = require("./commands/augment.js");
56
59
  const telegram_js_1 = require("./commands/telegram.js");
60
+ const query_js_1 = require("./commands/query.js");
57
61
  const utils_js_1 = require("./utils.js");
58
62
  // ── Apply ~/.sf/config.json to process.env BEFORE any command ────────────────
59
63
  // This means client.ts, kalshi.ts, agent.ts keep reading process.env and just work.
@@ -73,12 +77,16 @@ const GROUPED_HELP = `
73
77
  \x1b[1mThesis\x1b[22m
74
78
  \x1b[36mlist\x1b[39m List all theses
75
79
  \x1b[36mget\x1b[39m <id> Full thesis details
76
- \x1b[36mcontext\x1b[39m <id> [--json] Thesis snapshot \x1b[2m(primary for agents)\x1b[22m
80
+ \x1b[36mcontext\x1b[39m [id] [--json] Market snapshot (no id) or thesis context (with id)
77
81
  \x1b[36mcreate\x1b[39m "thesis" Create a new thesis
78
82
  \x1b[36msignal\x1b[39m <id> "content" Inject a signal
79
83
  \x1b[36mevaluate\x1b[39m <id> Trigger deep evaluation
84
+ \x1b[36maugment\x1b[39m <id> Evolve causal tree with new nodes
80
85
  \x1b[36mpublish\x1b[39m / \x1b[36munpublish\x1b[39m <id> Manage public visibility
81
86
 
87
+ \x1b[1mSearch\x1b[22m
88
+ \x1b[36mquery\x1b[39m "question" LLM-enhanced market knowledge search \x1b[2m(no auth)\x1b[22m
89
+
82
90
  \x1b[1mMarkets\x1b[22m
83
91
  \x1b[36mscan\x1b[39m "keywords" Search Kalshi + Polymarket
84
92
  \x1b[36mscan\x1b[39m --series TICKER Browse a Kalshi series
@@ -106,6 +114,7 @@ const GROUPED_HELP = `
106
114
 
107
115
  \x1b[1mInteractive\x1b[22m
108
116
  \x1b[36magent\x1b[39m [id] Agent with natural language + tools
117
+ \x1b[36mprompt\x1b[39m [id] Dynamic system prompt for any agent
109
118
  \x1b[36mtelegram\x1b[39m Telegram bot for monitoring
110
119
 
111
120
  \x1b[1mInfo\x1b[22m
@@ -123,15 +132,96 @@ program
123
132
  .option('--api-url <url>', 'API base URL (or set SF_API_URL env var)')
124
133
  .configureHelp({
125
134
  formatHelp: (cmd, helper) => {
126
- // For subcommands, use default help
127
135
  if (cmd.parent)
128
136
  return helper.formatHelp(cmd, helper);
129
- // For main program, show grouped help
130
137
  return GROUPED_HELP;
131
138
  },
132
- });
139
+ })
140
+ .action(async () => {
141
+ // `sf` with no args → interactive entry point
142
+ await interactiveEntry();
143
+ });
144
+ // ── Interactive entry (sf with no args) ──────────────────────────────────────
145
+ async function interactiveEntry() {
146
+ // Non-TTY stdin (pipe, CI) → show help instead of hanging
147
+ if (!process.stdin.isTTY) {
148
+ console.log(GROUPED_HELP);
149
+ return;
150
+ }
151
+ const readline = await import('readline');
152
+ // Scenario B: configured + has theses → go straight to agent
153
+ if ((0, config_js_1.isConfigured)()) {
154
+ try {
155
+ const client = new client_js_1.SFClient();
156
+ const { theses } = await client.listTheses();
157
+ const active = (theses || []).filter((t) => t.status === 'active');
158
+ if (active.length > 0) {
159
+ // Go straight to agent (like Claude Code — open and type)
160
+ await (0, agent_js_1.agentCommand)(active[0].id, {});
161
+ return;
162
+ }
163
+ }
164
+ catch {
165
+ // API error — fall through to prompt
166
+ }
167
+ }
168
+ // Scenario A/C: not configured or no theses → ask for input
169
+ console.log();
170
+ console.log(' \x1b[1mSimpleFunctions\x1b[22m — prediction market intelligence');
171
+ console.log();
172
+ if ((0, config_js_1.isConfigured)()) {
173
+ console.log(' \x1b[2mYou have no active theses.\x1b[22m');
174
+ }
175
+ else {
176
+ console.log(' \x1b[2mNo login needed to explore.\x1b[22m');
177
+ }
178
+ console.log();
179
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
180
+ const answer = await new Promise(resolve => {
181
+ rl.question(' What\'s your market view? (keyword or thesis)\n \x1b[36m>\x1b[39m ', ans => {
182
+ rl.close();
183
+ resolve(ans.trim());
184
+ });
185
+ });
186
+ if (!answer) {
187
+ // Empty input → show help
188
+ console.log(GROUPED_HELP);
189
+ return;
190
+ }
191
+ // Extract keywords and scan
192
+ let keywords = answer
193
+ .replace(/[^\w\s]/g, ' ')
194
+ .split(/\s+/)
195
+ .filter(w => w.length > 2)
196
+ .slice(0, 5)
197
+ .join(' ');
198
+ // If all words were too short, use the raw answer
199
+ if (!keywords)
200
+ keywords = answer.trim().slice(0, 40);
201
+ console.log();
202
+ console.log(` \x1b[2mScanning Kalshi + Polymarket for: "${keywords}"...\x1b[22m`);
203
+ console.log();
204
+ try {
205
+ const { scanCommand } = await import('./commands/scan.js');
206
+ await scanCommand(keywords, { json: false });
207
+ }
208
+ catch (err) {
209
+ console.error(` \x1b[31mScan failed: ${err.message}\x1b[39m`);
210
+ }
211
+ // Conversion hook
212
+ console.log();
213
+ if ((0, config_js_1.isConfigured)()) {
214
+ console.log(' \x1b[2mWant to monitor this thesis 24/7?\x1b[22m');
215
+ console.log(` \x1b[36msf create\x1b[39m "${answer.slice(0, 60)}"`);
216
+ }
217
+ else {
218
+ console.log(' \x1b[2mWant 24/7 monitoring + edge detection?\x1b[22m');
219
+ console.log(' \x1b[36msf setup\x1b[39m to get started (2 min)');
220
+ }
221
+ console.log();
222
+ }
133
223
  // ── Pre-action guard: check configuration ────────────────────────────────────
134
- const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan', 'explore', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history', 'liquidity']);
224
+ const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan', 'explore', 'query', 'context', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history', 'liquidity', 'book', 'prompt', 'sf']);
135
225
  program.hook('preAction', (thisCommand, actionCommand) => {
136
226
  const cmdName = actionCommand.name();
137
227
  if (NO_CONFIG_COMMANDS.has(cmdName))
@@ -141,8 +231,18 @@ program.hook('preAction', (thisCommand, actionCommand) => {
141
231
  if (g.apiKey)
142
232
  return;
143
233
  if (!(0, config_js_1.isConfigured)()) {
144
- console.log();
145
- console.log(' SimpleFunctions 未配置。运行 \x1b[36msf setup\x1b[39m 开始。');
234
+ const isJson = process.argv.includes('--json');
235
+ if (isJson) {
236
+ console.log(JSON.stringify({ error: 'API key required. Run sf setup or pass --api-key.', code: 'NOT_CONFIGURED' }));
237
+ }
238
+ else {
239
+ console.log();
240
+ console.log(' This command needs an API key. Two options:');
241
+ console.log(' 1. \x1b[36msf setup\x1b[39m Interactive wizard (2 min)');
242
+ console.log(' 2. \x1b[36msf --api-key KEY\x1b[39m Pass inline');
243
+ console.log();
244
+ console.log(' \x1b[2mDon\'t have a key? Try \x1b[36msf scan "oil"\x1b[22m\x1b[2m — works without login.\x1b[22m');
245
+ }
146
246
  console.log();
147
247
  process.exit(1);
148
248
  }
@@ -179,10 +279,10 @@ program
179
279
  const g = cmd.optsWithGlobals();
180
280
  await run(() => (0, get_js_1.getCommand)(id, { json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
181
281
  });
182
- // ── sf context <id> ───────────────────────────────────────────────────────────
282
+ // ── sf context [id] ───────────────────────────────────────────────────────────
183
283
  program
184
- .command('context <id>')
185
- .description('Get thesis context snapshot (primary command for agents)')
284
+ .command('context [id]')
285
+ .description('Context snapshot. With ID: thesis-specific. Without: global market snapshot (no auth)')
186
286
  .option('--json', 'Output raw JSON')
187
287
  .action(async (id, opts, cmd) => {
188
288
  const g = cmd.optsWithGlobals();
@@ -498,6 +598,42 @@ program
498
598
  }
499
599
  await run(() => (0, book_js_1.bookCommand)(tickers, opts));
500
600
  });
601
+ // ── sf prompt ────────────────────────────────────────────────────────────────
602
+ program
603
+ .command('prompt [thesisId]')
604
+ .description('Get dynamic system prompt with live thesis state (for agent injection)')
605
+ .option('--sections <list>', 'Include: thesis,tree,edges,evaluation,orderbook (default: all)')
606
+ .option('--max-length <n>', 'Max character length (default: 3000)')
607
+ .option('--json', 'JSON format with metadata')
608
+ .action(async (thesisId, opts, cmd) => {
609
+ const g = cmd.optsWithGlobals();
610
+ await run(() => (0, prompt_js_1.promptCommand)(thesisId, {
611
+ sections: opts.sections,
612
+ maxLength: opts.maxLength,
613
+ json: opts.json,
614
+ apiKey: g.apiKey,
615
+ apiUrl: g.apiUrl,
616
+ }));
617
+ });
618
+ // ── sf augment <thesisId> ────────────────────────────────────────────────────
619
+ program
620
+ .command('augment <thesisId>')
621
+ .description('Review & merge suggested causal tree nodes from evaluations')
622
+ .option('--dry-run', 'Preview accepted nodes without applying')
623
+ .option('--json', 'JSON output')
624
+ .action(async (thesisId, opts, cmd) => {
625
+ const g = cmd.optsWithGlobals();
626
+ await run(() => (0, augment_js_1.augmentCommand)(thesisId, { dryRun: opts.dryRun, json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
627
+ });
628
+ // ── sf query "question" ──────────────────────────────────────────────────────
629
+ program
630
+ .command('query <question>')
631
+ .description('LLM-enhanced prediction market knowledge search (no auth required)')
632
+ .option('--json', 'JSON output')
633
+ .option('--limit <n>', 'Max results per category (default 10)', '10')
634
+ .action(async (question, opts) => {
635
+ await run(() => (0, query_js_1.queryCommand)(question, { json: opts.json, limit: opts.limit }));
636
+ });
501
637
  // ── sf telegram ──────────────────────────────────────────────────────────────
502
638
  program
503
639
  .command('telegram')
@@ -38,6 +38,26 @@ async function buildTools(sfClient, thesisId, latestContext) {
38
38
  return { content: [{ type: 'text', text: JSON.stringify(ctx, null, 2) }], details: {} };
39
39
  },
40
40
  },
41
+ {
42
+ name: 'global_context', label: 'Snapshot',
43
+ description: 'Global market snapshot — movers, expiring, milestones, liquidity, signals. No thesis needed.',
44
+ parameters: emptyParams,
45
+ execute: async () => {
46
+ const { fetchGlobalContext } = await import('../client.js');
47
+ const data = await fetchGlobalContext();
48
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
49
+ },
50
+ },
51
+ {
52
+ name: 'query', label: 'Query',
53
+ description: 'LLM-enhanced prediction market search. Returns answer, live prices, key factors.',
54
+ parameters: Type.Object({ q: Type.String({ description: 'Question' }) }),
55
+ execute: async (_id, p) => {
56
+ const { fetchQuery } = await import('../client.js');
57
+ const data = await fetchQuery(p.q);
58
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], details: {} };
59
+ },
60
+ },
41
61
  {
42
62
  name: 'inject_signal', label: 'Signal',
43
63
  description: 'Inject a signal (news, note, observation) into the thesis',
@@ -363,13 +383,18 @@ async function getOrCreateAgent(sfClient, session) {
363
383
  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.
364
384
 
365
385
  ## Framework
366
- Edge = thesis price - market price. Positive = market underprices. Negative = overpriced.
367
- Contracts with large edge + good liquidity = most tradeable.
386
+ Edge = thesis price - market price. Positive = market underprices.
387
+ Edge types: [consensus] depth>=500 real opponents, [attention] depth<100 illusory, [timing] market lags, [risk_premium] platform risk. Tag when reporting.
388
+ Price: depth >= 500 = consensus, < 100 = unreliable, spread > 5 = noisy.
368
389
 
369
390
  ## Rules
391
+ - When reporting an edge, tag it: [consensus] [attention] [timing] [risk_premium].
392
+ - If any kill condition is triggered or approaching, lead with that.
393
+ - If nothing material to do, say "quiet — no action needed." Don't pad.
370
394
  - Keep Telegram messages SHORT — bullet points, no walls of text.
371
395
  - Prices in cents (¢). P&L in dollars ($). Don't re-convert tool output units.
372
396
  - Call tools for fresh data. Never guess prices or P&L from this prompt.
397
+ - You don't know user's positions. Call get_positions before discussing trades.
373
398
  - If user mentions news, inject_signal immediately. Don't ask "should I?"
374
399
  - If user says "evaluate" or "run it", trigger immediately.
375
400
  - Don't end with "anything else?" — user will ask.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.5.3",
3
+ "version": "1.7.1",
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"