@spfunctions/cli 0.1.7 → 1.1.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/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +223 -20
- package/dist/index.js +3 -2
- package/package.json +9 -5
package/dist/commands/agent.d.ts
CHANGED
package/dist/commands/agent.js
CHANGED
|
@@ -13,10 +13,43 @@
|
|
|
13
13
|
* Slash commands (bypass LLM):
|
|
14
14
|
* /help /tree /edges /pos /eval /model /clear /exit
|
|
15
15
|
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
20
|
exports.agentCommand = agentCommand;
|
|
21
|
+
const fs_1 = __importDefault(require("fs"));
|
|
22
|
+
const path_1 = __importDefault(require("path"));
|
|
23
|
+
const os_1 = __importDefault(require("os"));
|
|
18
24
|
const client_js_1 = require("../client.js");
|
|
19
25
|
const kalshi_js_1 = require("../kalshi.js");
|
|
26
|
+
// ─── Session persistence ─────────────────────────────────────────────────────
|
|
27
|
+
function getSessionDir() {
|
|
28
|
+
return path_1.default.join(os_1.default.homedir(), '.sf', 'sessions');
|
|
29
|
+
}
|
|
30
|
+
function getSessionPath(thesisId) {
|
|
31
|
+
return path_1.default.join(getSessionDir(), `${thesisId}.json`);
|
|
32
|
+
}
|
|
33
|
+
function loadSession(thesisId) {
|
|
34
|
+
const p = getSessionPath(thesisId);
|
|
35
|
+
try {
|
|
36
|
+
if (fs_1.default.existsSync(p)) {
|
|
37
|
+
return JSON.parse(fs_1.default.readFileSync(p, 'utf-8'));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch { /* corrupt file, ignore */ }
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function saveSession(thesisId, model, messages) {
|
|
44
|
+
const dir = getSessionDir();
|
|
45
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
46
|
+
fs_1.default.writeFileSync(getSessionPath(thesisId), JSON.stringify({
|
|
47
|
+
thesisId,
|
|
48
|
+
model,
|
|
49
|
+
updatedAt: new Date().toISOString(),
|
|
50
|
+
messages,
|
|
51
|
+
}, null, 2));
|
|
52
|
+
}
|
|
20
53
|
// ─── ANSI 24-bit color helpers (no chalk dependency) ─────────────────────────
|
|
21
54
|
const rgb = (r, g, b) => (s) => `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m`;
|
|
22
55
|
const bgRgb = (r, g, b) => (s) => `\x1b[48;2;${r};${g};${b}m${s}\x1b[49m`;
|
|
@@ -368,9 +401,12 @@ async function agentCommand(thesisId, opts) {
|
|
|
368
401
|
{ name: 'edges', description: 'Display edge/spread table' },
|
|
369
402
|
{ name: 'pos', description: 'Display Kalshi positions' },
|
|
370
403
|
{ name: 'eval', description: 'Trigger deep evaluation' },
|
|
404
|
+
{ name: 'switch', description: 'Switch thesis (e.g. /switch f582bf76)' },
|
|
405
|
+
{ name: 'compact', description: 'Compress conversation history' },
|
|
406
|
+
{ name: 'new', description: 'Start fresh session' },
|
|
371
407
|
{ name: 'model', description: 'Switch model (e.g. /model anthropic/claude-sonnet-4)' },
|
|
372
|
-
{ name: 'clear', description: 'Clear
|
|
373
|
-
{ name: 'exit', description: 'Exit agent' },
|
|
408
|
+
{ name: 'clear', description: 'Clear screen (keeps history)' },
|
|
409
|
+
{ name: 'exit', description: 'Exit agent (auto-saves)' },
|
|
374
410
|
], process.cwd());
|
|
375
411
|
editor.setAutocompleteProvider(autocompleteProvider);
|
|
376
412
|
// Assemble TUI tree
|
|
@@ -535,17 +571,60 @@ async function agentCommand(thesisId, opts) {
|
|
|
535
571
|
},
|
|
536
572
|
},
|
|
537
573
|
];
|
|
538
|
-
// ── System prompt
|
|
539
|
-
|
|
574
|
+
// ── System prompt builder ──────────────────────────────────────────────────
|
|
575
|
+
function buildSystemPrompt(ctx) {
|
|
576
|
+
const edgesSummary = ctx.edges
|
|
577
|
+
?.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
|
|
578
|
+
.slice(0, 5)
|
|
579
|
+
.map((e) => ` ${(e.market || '').slice(0, 40)} | ${e.venue || 'kalshi'} | mkt ${e.marketPrice}\u00A2 \u2192 thesis ${e.thesisPrice}\u00A2 | edge ${e.edge > 0 ? '+' : ''}${e.edge} | ${e.orderbook?.liquidityScore || '?'}`)
|
|
580
|
+
.join('\n') || ' (no edge data)';
|
|
581
|
+
const nodesSummary = ctx.causalTree?.nodes
|
|
582
|
+
?.filter((n) => n.depth === 0)
|
|
583
|
+
.map((n) => ` ${n.id} ${(n.label || '').slice(0, 40)} \u2014 ${Math.round(n.probability * 100)}%`)
|
|
584
|
+
.join('\n') || ' (no causal tree)';
|
|
585
|
+
const conf = typeof ctx.confidence === 'number'
|
|
586
|
+
? Math.round(ctx.confidence * 100)
|
|
587
|
+
: (typeof ctx.confidence === 'string' ? parseInt(ctx.confidence) : 0);
|
|
588
|
+
return `You are a prediction market trading assistant. Your job is not to please the user \u2014 it is to help them see reality clearly and make correct trading decisions.
|
|
589
|
+
|
|
590
|
+
## Your analytical framework
|
|
591
|
+
|
|
592
|
+
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.
|
|
593
|
+
|
|
594
|
+
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.
|
|
595
|
+
|
|
596
|
+
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.
|
|
597
|
+
|
|
598
|
+
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.
|
|
599
|
+
|
|
600
|
+
## Your behavioral rules
|
|
601
|
+
|
|
602
|
+
- Think before calling tools. If the data is already in context, don't re-fetch.
|
|
603
|
+
- If the user asks about positions, check if Kalshi is configured first. If not, say so directly.
|
|
604
|
+
- If the user says "note this" or mentions a news event, inject a signal. Don't ask "should I note this?"
|
|
605
|
+
- If the user says "evaluate" or "run it", trigger immediately. Don't confirm.
|
|
606
|
+
- Don't end every response with "anything else?" \u2014 the user will ask when they want to.
|
|
607
|
+
- If you notice an edge narrowing or disappearing, say so proactively. Don't only report good news.
|
|
608
|
+
- If a causal tree node probability seriously contradicts the market price, point it out.
|
|
609
|
+
- Use Chinese if the user writes in Chinese, English if they write in English.
|
|
610
|
+
- Align tables. Be precise with numbers to the cent.
|
|
540
611
|
|
|
541
|
-
Current thesis
|
|
542
|
-
Confidence: ${confidencePct}%
|
|
543
|
-
Status: ${latestContext.status}
|
|
544
|
-
Thesis ID: ${latestContext.thesisId || resolvedThesisId}
|
|
612
|
+
## Current thesis state
|
|
545
613
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
614
|
+
Thesis: ${ctx.thesis || ctx.rawThesis || 'N/A'}
|
|
615
|
+
ID: ${ctx.thesisId || resolvedThesisId}
|
|
616
|
+
Confidence: ${conf}%
|
|
617
|
+
Status: ${ctx.status}
|
|
618
|
+
|
|
619
|
+
Top-level causal tree nodes:
|
|
620
|
+
${nodesSummary}
|
|
621
|
+
|
|
622
|
+
Top 5 edges by magnitude:
|
|
623
|
+
${edgesSummary}
|
|
624
|
+
|
|
625
|
+
${ctx.lastEvaluation?.summary ? `Latest evaluation summary: ${ctx.lastEvaluation.summary.slice(0, 300)}` : ''}`;
|
|
626
|
+
}
|
|
627
|
+
const systemPrompt = buildSystemPrompt(latestContext);
|
|
549
628
|
// ── Create Agent ───────────────────────────────────────────────────────────
|
|
550
629
|
const agent = new Agent({
|
|
551
630
|
initialState: {
|
|
@@ -561,6 +640,30 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
561
640
|
return undefined;
|
|
562
641
|
},
|
|
563
642
|
});
|
|
643
|
+
// ── Session restore ────────────────────────────────────────────────────────
|
|
644
|
+
let sessionRestored = false;
|
|
645
|
+
if (!opts?.newSession) {
|
|
646
|
+
const saved = loadSession(resolvedThesisId);
|
|
647
|
+
if (saved?.messages?.length > 0) {
|
|
648
|
+
try {
|
|
649
|
+
agent.replaceMessages(saved.messages);
|
|
650
|
+
// Always update system prompt with fresh context
|
|
651
|
+
agent.setSystemPrompt(systemPrompt);
|
|
652
|
+
sessionRestored = true;
|
|
653
|
+
}
|
|
654
|
+
catch { /* corrupt session, start fresh */ }
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Helper to persist session after each turn
|
|
658
|
+
function persistSession() {
|
|
659
|
+
try {
|
|
660
|
+
const msgs = agent.state.messages;
|
|
661
|
+
if (msgs.length > 0) {
|
|
662
|
+
saveSession(resolvedThesisId, currentModelName, msgs);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
catch { /* best-effort save */ }
|
|
666
|
+
}
|
|
564
667
|
// ── Subscribe to agent events → update TUI ────────────────────────────────
|
|
565
668
|
let currentAssistantMd = null;
|
|
566
669
|
let currentAssistantText = '';
|
|
@@ -633,6 +736,7 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
633
736
|
if (event.type === 'agent_end') {
|
|
634
737
|
// Agent turn fully complete — safe to accept new input
|
|
635
738
|
isProcessing = false;
|
|
739
|
+
persistSession();
|
|
636
740
|
flushRender();
|
|
637
741
|
}
|
|
638
742
|
if (event.type === 'tool_execution_start') {
|
|
@@ -671,14 +775,17 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
671
775
|
case '/help': {
|
|
672
776
|
addSpacer();
|
|
673
777
|
addSystemText(C.zinc200(bold('Commands')) + '\n' +
|
|
674
|
-
C.emerald('/help
|
|
675
|
-
C.emerald('/tree
|
|
676
|
-
C.emerald('/edges
|
|
677
|
-
C.emerald('/pos
|
|
678
|
-
C.emerald('/eval
|
|
679
|
-
C.emerald('/
|
|
680
|
-
C.emerald('/
|
|
681
|
-
C.emerald('/
|
|
778
|
+
C.emerald('/help ') + C.zinc400('Show this help') + '\n' +
|
|
779
|
+
C.emerald('/tree ') + C.zinc400('Display causal tree') + '\n' +
|
|
780
|
+
C.emerald('/edges ') + C.zinc400('Display edge/spread table') + '\n' +
|
|
781
|
+
C.emerald('/pos ') + C.zinc400('Display Kalshi positions') + '\n' +
|
|
782
|
+
C.emerald('/eval ') + C.zinc400('Trigger deep evaluation') + '\n' +
|
|
783
|
+
C.emerald('/switch <id>') + C.zinc400(' Switch thesis') + '\n' +
|
|
784
|
+
C.emerald('/compact ') + C.zinc400('Compress conversation history') + '\n' +
|
|
785
|
+
C.emerald('/new ') + C.zinc400('Start fresh session') + '\n' +
|
|
786
|
+
C.emerald('/model <m> ') + C.zinc400('Switch model') + '\n' +
|
|
787
|
+
C.emerald('/clear ') + C.zinc400('Clear screen (keeps history)') + '\n' +
|
|
788
|
+
C.emerald('/exit ') + C.zinc400('Exit (auto-saves)'));
|
|
682
789
|
addSpacer();
|
|
683
790
|
return true;
|
|
684
791
|
}
|
|
@@ -766,6 +873,98 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
766
873
|
tui.requestRender();
|
|
767
874
|
return true;
|
|
768
875
|
}
|
|
876
|
+
case '/switch': {
|
|
877
|
+
const newId = parts[1]?.trim();
|
|
878
|
+
if (!newId) {
|
|
879
|
+
addSystemText(C.zinc400('Usage: /switch <thesisId>'));
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
addSpacer();
|
|
883
|
+
try {
|
|
884
|
+
// Save current session
|
|
885
|
+
persistSession();
|
|
886
|
+
// Load new thesis context
|
|
887
|
+
const newContext = await sfClient.getContext(newId);
|
|
888
|
+
resolvedThesisId = newContext.thesisId || newId;
|
|
889
|
+
latestContext = newContext;
|
|
890
|
+
// Build new system prompt using the rich builder
|
|
891
|
+
const newSysPrompt = buildSystemPrompt(newContext);
|
|
892
|
+
const newConf = typeof newContext.confidence === 'number'
|
|
893
|
+
? Math.round(newContext.confidence * 100) : 0;
|
|
894
|
+
// Load saved session or start fresh
|
|
895
|
+
const saved = loadSession(resolvedThesisId);
|
|
896
|
+
if (saved?.messages?.length > 0) {
|
|
897
|
+
agent.replaceMessages(saved.messages);
|
|
898
|
+
agent.setSystemPrompt(newSysPrompt);
|
|
899
|
+
addSystemText(C.emerald(`Switched to ${resolvedThesisId.slice(0, 8)}`) + C.zinc400(` (resumed ${saved.messages.length} messages)`));
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
agent.clearMessages();
|
|
903
|
+
agent.setSystemPrompt(newSysPrompt);
|
|
904
|
+
addSystemText(C.emerald(`Switched to ${resolvedThesisId.slice(0, 8)}`) + C.zinc400(' (new session)'));
|
|
905
|
+
}
|
|
906
|
+
// Update header
|
|
907
|
+
headerBar.update(C.emerald(bold('SF Agent')) + C.zinc600(` \u2014 ${resolvedThesisId.slice(0, 8)}`), newConf > 0 ? C.zinc200(`${newConf}%`) : '', undefined);
|
|
908
|
+
chatContainer.clear();
|
|
909
|
+
const thText = (newContext.thesis || newContext.rawThesis || '').slice(0, 120);
|
|
910
|
+
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
911
|
+
C.zinc200(bold(thText)) + '\n' +
|
|
912
|
+
C.zinc600(`${newContext.status || 'active'} ${newConf > 0 ? newConf + '%' : ''} ${(newContext.edges || []).length} edges`) + '\n' +
|
|
913
|
+
C.zinc600('\u2500'.repeat(50)));
|
|
914
|
+
}
|
|
915
|
+
catch (err) {
|
|
916
|
+
addSystemText(C.red(`Switch failed: ${err.message}`));
|
|
917
|
+
}
|
|
918
|
+
addSpacer();
|
|
919
|
+
tui.requestRender();
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
case '/compact': {
|
|
923
|
+
addSpacer();
|
|
924
|
+
const msgs = agent.state.messages;
|
|
925
|
+
if (msgs.length <= 10) {
|
|
926
|
+
addSystemText(C.zinc400('Conversation too short to compact'));
|
|
927
|
+
addSpacer();
|
|
928
|
+
return true;
|
|
929
|
+
}
|
|
930
|
+
// Keep recent 6 messages (3 turns) + create summary of the rest
|
|
931
|
+
const recentCount = 6;
|
|
932
|
+
const toCompress = msgs.slice(0, -recentCount);
|
|
933
|
+
const toKeep = msgs.slice(-recentCount);
|
|
934
|
+
// Extract text for summary (no LLM, just bullet points)
|
|
935
|
+
const bulletPoints = [];
|
|
936
|
+
for (const m of toCompress) {
|
|
937
|
+
const content = typeof m.content === 'string' ? m.content : '';
|
|
938
|
+
if (m.role === 'user' && content) {
|
|
939
|
+
bulletPoints.push(`- User: ${content.slice(0, 100)}`);
|
|
940
|
+
}
|
|
941
|
+
else if (m.role === 'assistant' && content) {
|
|
942
|
+
bulletPoints.push(`- Assistant: ${content.slice(0, 150)}`);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const summary = bulletPoints.slice(-20).join('\n');
|
|
946
|
+
// Replace messages: summary + recent
|
|
947
|
+
const compactedMessages = [
|
|
948
|
+
{ role: 'assistant', content: `[Conversation summary - ${toCompress.length} messages compressed]\n${summary}` },
|
|
949
|
+
...toKeep,
|
|
950
|
+
];
|
|
951
|
+
agent.replaceMessages(compactedMessages);
|
|
952
|
+
persistSession();
|
|
953
|
+
addSystemText(C.emerald(`Compacted: ${toCompress.length} messages \u2192 summary + ${toKeep.length} recent`));
|
|
954
|
+
addSpacer();
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
case '/new': {
|
|
958
|
+
addSpacer();
|
|
959
|
+
persistSession(); // save current before clearing
|
|
960
|
+
agent.clearMessages();
|
|
961
|
+
agent.setSystemPrompt(systemPrompt);
|
|
962
|
+
chatContainer.clear();
|
|
963
|
+
addSystemText(C.emerald('Session cleared') + C.zinc400(' \u2014 fresh start'));
|
|
964
|
+
addSpacer();
|
|
965
|
+
tui.requestRender();
|
|
966
|
+
return true;
|
|
967
|
+
}
|
|
769
968
|
case '/clear': {
|
|
770
969
|
chatContainer.clear();
|
|
771
970
|
tui.requestRender();
|
|
@@ -821,6 +1020,7 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
821
1020
|
function cleanup() {
|
|
822
1021
|
if (currentLoader)
|
|
823
1022
|
currentLoader.stop();
|
|
1023
|
+
persistSession();
|
|
824
1024
|
tui.stop();
|
|
825
1025
|
process.exit(0);
|
|
826
1026
|
}
|
|
@@ -839,9 +1039,12 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
839
1039
|
// ── Show initial welcome ───────────────────────────────────────────────────
|
|
840
1040
|
const thesisText = latestContext.thesis || latestContext.rawThesis || 'N/A';
|
|
841
1041
|
const truncatedThesis = thesisText.length > 120 ? thesisText.slice(0, 120) + '...' : thesisText;
|
|
1042
|
+
const sessionStatus = sessionRestored
|
|
1043
|
+
? C.zinc600(` resumed (${agent.state.messages.length} messages)`)
|
|
1044
|
+
: C.zinc600(' new session');
|
|
842
1045
|
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
843
1046
|
C.zinc200(bold(truncatedThesis)) + '\n' +
|
|
844
|
-
C.zinc600(`${latestContext.status || 'active'} ${confidencePct > 0 ? confidencePct + '%' : ''} ${(latestContext.edges || []).length} edges`) + '\n' +
|
|
1047
|
+
C.zinc600(`${latestContext.status || 'active'} ${confidencePct > 0 ? confidencePct + '%' : ''} ${(latestContext.edges || []).length} edges`) + sessionStatus + '\n' +
|
|
845
1048
|
C.zinc600('\u2500'.repeat(50)));
|
|
846
1049
|
addSpacer();
|
|
847
1050
|
// ── Start TUI ──────────────────────────────────────────────────────────────
|
package/dist/index.js
CHANGED
|
@@ -135,11 +135,12 @@ program
|
|
|
135
135
|
program
|
|
136
136
|
.command('agent [thesisId]')
|
|
137
137
|
.description('Interactive agent mode — natural language interface to SimpleFunctions')
|
|
138
|
-
.option('--model <model>', 'Model via OpenRouter (default: anthropic/claude-sonnet-4
|
|
138
|
+
.option('--model <model>', 'Model via OpenRouter (default: anthropic/claude-sonnet-4.6)')
|
|
139
139
|
.option('--model-key <key>', 'OpenRouter API key (or set OPENROUTER_API_KEY)')
|
|
140
|
+
.option('--new', 'Start a fresh session (default: continue last session)')
|
|
140
141
|
.action(async (thesisId, opts, cmd) => {
|
|
141
142
|
const g = cmd.optsWithGlobals();
|
|
142
|
-
await run(() => (0, agent_js_1.agentCommand)(thesisId, { model: opts.model, modelKey: opts.modelKey }));
|
|
143
|
+
await run(() => (0, agent_js_1.agentCommand)(thesisId, { model: opts.model, modelKey: opts.modelKey, newSession: opts.new }));
|
|
143
144
|
});
|
|
144
145
|
// ── Error wrapper ─────────────────────────────────────────────────────────────
|
|
145
146
|
async function run(fn) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfunctions/cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.2",
|
|
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"
|
|
7
7
|
},
|
|
@@ -26,11 +26,15 @@
|
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
28
|
"keywords": [
|
|
29
|
-
"prediction-
|
|
30
|
-
"thesis-agent",
|
|
29
|
+
"prediction-market",
|
|
31
30
|
"kalshi",
|
|
32
31
|
"polymarket",
|
|
33
|
-
"
|
|
32
|
+
"trading",
|
|
33
|
+
"cli",
|
|
34
|
+
"agent",
|
|
35
|
+
"orderbook",
|
|
36
|
+
"market-intelligence",
|
|
37
|
+
"edge-detection"
|
|
34
38
|
],
|
|
35
39
|
"license": "MIT",
|
|
36
40
|
"repository": {
|