@spfunctions/cli 1.5.2 → 1.6.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 +1 -0
- package/dist/client.js +4 -0
- package/dist/commands/agent.js +51 -6
- package/dist/commands/augment.d.ts +12 -0
- package/dist/commands/augment.js +56 -0
- package/dist/commands/prompt.d.ts +13 -0
- package/dist/commands/prompt.js +35 -0
- package/dist/commands/query.d.ts +15 -0
- package/dist/commands/query.js +87 -0
- package/dist/commands/scan.js +5 -0
- package/dist/commands/signal.d.ts +1 -0
- package/dist/commands/signal.js +4 -1
- package/dist/index.js +144 -7
- package/dist/telegram/agent-bridge.js +7 -2
- package/package.json +1 -1
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>;
|
package/dist/client.js
CHANGED
|
@@ -86,6 +86,10 @@ class SFClient {
|
|
|
86
86
|
async unpublish(id) {
|
|
87
87
|
return this.request('DELETE', `/api/thesis/${id}/publish`);
|
|
88
88
|
}
|
|
89
|
+
async augmentTree(id, dryRun = false) {
|
|
90
|
+
const qs = dryRun ? '?dryRun=true' : '';
|
|
91
|
+
return this.request('POST', `/api/thesis/${id}/augment${qs}`);
|
|
92
|
+
}
|
|
89
93
|
// ── Strategy operations ──
|
|
90
94
|
async getStrategies(id, status) {
|
|
91
95
|
const qs = status ? `?status=${status}` : '';
|
package/dist/commands/agent.js
CHANGED
|
@@ -1570,16 +1570,48 @@ async function agentCommand(thesisId, opts) {
|
|
|
1570
1570
|
|
|
1571
1571
|
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
1572
|
|
|
1573
|
-
Edge = thesis-implied price - actual market price. Positive edge
|
|
1573
|
+
Edge = thesis-implied price - actual market price. Positive edge = market underprices. Contracts with large edges AND good liquidity are most tradeable.
|
|
1574
1574
|
|
|
1575
|
-
executableEdge
|
|
1575
|
+
executableEdge = real edge after subtracting bid-ask spread. Big theoretical edge with wide spread may not be worth entering.
|
|
1576
1576
|
|
|
1577
|
-
|
|
1577
|
+
### Edge diagnosis (always classify)
|
|
1578
|
+
When reporting an edge, classify it:
|
|
1579
|
+
- "consensus gap": depth >= 500, market actively disagrees — real edge, real opponents
|
|
1580
|
+
- "attention gap": depth < 100, no real pricing — edge may be illusory, needs catalyst
|
|
1581
|
+
- "timing gap": market directionally agrees but lags — may close suddenly on news
|
|
1582
|
+
- "risk premium": edge reflects settlement ambiguity or platform risk, not alpha
|
|
1583
|
+
For edges > 20 cents, state in one sentence what must be true for the market to be right and the thesis wrong.
|
|
1584
|
+
|
|
1585
|
+
### Price reliability
|
|
1586
|
+
47 cents with depth 14K = strong consensus. 47 cents with depth 50 = three people's opinion.
|
|
1587
|
+
- depth >= 500: reliable, treat as market consensus
|
|
1588
|
+
- depth 100-500: moderate confidence, caveat when citing
|
|
1589
|
+
- depth < 100: unreliable — do NOT treat as "the market thinks X"
|
|
1590
|
+
- spread > 5 cents: wide disagreement, noisy midpoint
|
|
1591
|
+
- liquidityScore = low: do NOT recommend entry
|
|
1592
|
+
|
|
1593
|
+
### Kill condition awareness
|
|
1594
|
+
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.
|
|
1595
|
+
|
|
1596
|
+
### Catalyst and time
|
|
1597
|
+
When discussing an edge, always state contract expiry and next identifiable catalyst. No visible catalyst = flag capital lock risk.
|
|
1598
|
+
|
|
1599
|
+
### Research workflow
|
|
1600
|
+
For complex questions, chain multiple tool calls:
|
|
1601
|
+
1. get_context 2. inspect_book 3. get_liquidity 4. web_search 5. synthesize
|
|
1602
|
+
Don't answer a complex question with a single tool call.
|
|
1603
|
+
|
|
1604
|
+
### Conditional rules
|
|
1605
|
+
- Portfolio/positions questions: flag correlated exposure — positions sharing upstream causal nodes are not independent bets.
|
|
1606
|
+
- No catalyst visible within 30 days + edge not improving: flag "stale capital risk."
|
|
1607
|
+
- Edges < 10 cents, thin liquidity, no catalyst near: say "nothing to do right now." Do not manufacture urgency.
|
|
1608
|
+
|
|
1609
|
+
Short-term markets settle into hard data that calibrates the thesis. Use them to verify node probabilities, not to bet.
|
|
1578
1610
|
|
|
1579
1611
|
## Your behavioral rules
|
|
1580
1612
|
|
|
1613
|
+
- 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
1614
|
- 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
1615
|
- If the user says "note this" or mentions a news event, inject a signal. Don't ask "should I note this?"
|
|
1584
1616
|
- If the user says "evaluate" or "run it", trigger immediately. Don't confirm.
|
|
1585
1617
|
- Don't end every response with "anything else?" \u2014 the user will ask when they want to.
|
|
@@ -3183,11 +3215,24 @@ async function runPlainTextAgent(params) {
|
|
|
3183
3215
|
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
3216
|
|
|
3185
3217
|
## Framework
|
|
3186
|
-
|
|
3187
|
-
|
|
3218
|
+
Edge = thesis price - market price. Positive = market underprices. executableEdge = edge minus spread.
|
|
3219
|
+
|
|
3220
|
+
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.
|
|
3221
|
+
For edges > 20 cents, state what the market must believe for it to be right.
|
|
3222
|
+
|
|
3223
|
+
Price reliability: depth >= 500 = consensus. depth < 100 = unreliable. spread > 5 cents = noisy. Flag illiquid markets.
|
|
3224
|
+
|
|
3225
|
+
Kill conditions: each causal node has a falsifier. Check these first when evaluating news. If triggered, override other analysis.
|
|
3226
|
+
|
|
3227
|
+
Always state contract expiry and next catalyst. No catalyst = flag capital lock risk.
|
|
3228
|
+
|
|
3229
|
+
For complex questions, chain: get_context -> inspect_book -> get_liquidity -> web_search -> synthesize.
|
|
3230
|
+
|
|
3231
|
+
Flag correlated exposure across positions sharing upstream nodes. If nothing to do, say so.
|
|
3188
3232
|
|
|
3189
3233
|
## Rules
|
|
3190
3234
|
- Be concise. Use tools for fresh data. Don't guess prices.
|
|
3235
|
+
- You do NOT know the user's positions at start. Call get_positions before discussing trades or portfolio.
|
|
3191
3236
|
- If user mentions news, inject_signal immediately.
|
|
3192
3237
|
- If user says "evaluate", trigger immediately. Don't confirm.
|
|
3193
3238
|
- 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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/commands/scan.js
CHANGED
|
@@ -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/commands/signal.js
CHANGED
|
@@ -7,6 +7,10 @@ async function signalCommand(id, content, opts) {
|
|
|
7
7
|
const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
|
|
8
8
|
const type = opts.type || 'user_note';
|
|
9
9
|
const result = await client.injectSignal(id, type, content, 'cli');
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify({ signalId: result.signalId || null, type, source: 'cli', content }, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
10
14
|
console.log(`${utils_js_1.c.green}✓${utils_js_1.c.reset} Signal injected`);
|
|
11
15
|
console.log(` ${utils_js_1.c.bold}Type:${utils_js_1.c.reset} ${type}`);
|
|
12
16
|
console.log(` ${utils_js_1.c.bold}Source:${utils_js_1.c.reset} cli`);
|
|
@@ -14,7 +18,6 @@ async function signalCommand(id, content, opts) {
|
|
|
14
18
|
if (result.signalId) {
|
|
15
19
|
console.log(` ${utils_js_1.c.bold}ID:${utils_js_1.c.reset} ${result.signalId}`);
|
|
16
20
|
}
|
|
17
|
-
// Calculate minutes until next 15-min cron cycle (runs at :00, :15, :30, :45)
|
|
18
21
|
const now = new Date();
|
|
19
22
|
const minute = now.getMinutes();
|
|
20
23
|
const nextCycleMin = Math.ceil((minute + 1) / 15) * 15;
|
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.
|
|
@@ -77,8 +81,12 @@ const GROUPED_HELP = `
|
|
|
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', '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
|
-
|
|
145
|
-
|
|
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
|
}
|
|
@@ -203,9 +303,10 @@ program
|
|
|
203
303
|
.command('signal <id> <content>')
|
|
204
304
|
.description('Inject a signal into the thesis queue')
|
|
205
305
|
.option('--type <type>', 'Signal type: news | user_note | external', 'user_note')
|
|
306
|
+
.option('--json', 'JSON output')
|
|
206
307
|
.action(async (id, content, opts, cmd) => {
|
|
207
308
|
const g = cmd.optsWithGlobals();
|
|
208
|
-
await run(() => (0, signal_js_1.signalCommand)(id, content, { type: opts.type, apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
309
|
+
await run(() => (0, signal_js_1.signalCommand)(id, content, { type: opts.type, json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
209
310
|
});
|
|
210
311
|
// ── sf evaluate <id> ──────────────────────────────────────────────────────────
|
|
211
312
|
program
|
|
@@ -497,6 +598,42 @@ program
|
|
|
497
598
|
}
|
|
498
599
|
await run(() => (0, book_js_1.bookCommand)(tickers, opts));
|
|
499
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
|
+
});
|
|
500
637
|
// ── sf telegram ──────────────────────────────────────────────────────────────
|
|
501
638
|
program
|
|
502
639
|
.command('telegram')
|
|
@@ -363,13 +363,18 @@ async function getOrCreateAgent(sfClient, session) {
|
|
|
363
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.
|
|
364
364
|
|
|
365
365
|
## Framework
|
|
366
|
-
Edge = thesis price - market price. Positive = market underprices.
|
|
367
|
-
|
|
366
|
+
Edge = thesis price - market price. Positive = market underprices.
|
|
367
|
+
Edge types: [consensus] depth>=500 real opponents, [attention] depth<100 illusory, [timing] market lags, [risk_premium] platform risk. Tag when reporting.
|
|
368
|
+
Price: depth >= 500 = consensus, < 100 = unreliable, spread > 5 = noisy.
|
|
368
369
|
|
|
369
370
|
## Rules
|
|
371
|
+
- When reporting an edge, tag it: [consensus] [attention] [timing] [risk_premium].
|
|
372
|
+
- If any kill condition is triggered or approaching, lead with that.
|
|
373
|
+
- If nothing material to do, say "quiet — no action needed." Don't pad.
|
|
370
374
|
- Keep Telegram messages SHORT — bullet points, no walls of text.
|
|
371
375
|
- Prices in cents (¢). P&L in dollars ($). Don't re-convert tool output units.
|
|
372
376
|
- Call tools for fresh data. Never guess prices or P&L from this prompt.
|
|
377
|
+
- You don't know user's positions. Call get_positions before discussing trades.
|
|
373
378
|
- If user mentions news, inject_signal immediately. Don't ask "should I?"
|
|
374
379
|
- If user says "evaluate" or "run it", trigger immediately.
|
|
375
380
|
- 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.
|
|
3
|
+
"version": "1.6.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"
|