@spfunctions/cli 0.1.5 → 0.1.6
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 +10 -10
- package/dist/commands/agent.js +631 -92
- package/package.json +3 -2
package/dist/commands/agent.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* sf agent — Interactive
|
|
2
|
+
* sf agent — Interactive TUI agent powered by pi-tui + pi-agent-core.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* [Header overlay] — thesis id, confidence, model
|
|
6
|
+
* [Spacer] — room for header
|
|
7
|
+
* [Chat container] — messages (user, assistant, tool, system)
|
|
8
|
+
* [Editor] — multi-line input with slash command autocomplete
|
|
9
|
+
* [Spacer] — room for footer
|
|
10
|
+
* [Footer overlay] — tokens, cost, tool count, /help hint
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* inject_signal — inject news/note/external signal
|
|
10
|
-
* trigger_evaluation — trigger deep eval (heavy model)
|
|
11
|
-
* scan_markets — search Kalshi markets
|
|
12
|
-
* list_theses — list all theses
|
|
13
|
-
* get_positions — Kalshi portfolio positions
|
|
12
|
+
* Slash commands (bypass LLM):
|
|
13
|
+
* /help /tree /edges /pos /eval /model /clear /exit
|
|
14
14
|
*/
|
|
15
15
|
export declare function agentCommand(thesisId?: string, opts?: {
|
|
16
16
|
model?: string;
|
package/dist/commands/agent.js
CHANGED
|
@@ -1,32 +1,266 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* sf agent — Interactive
|
|
3
|
+
* sf agent — Interactive TUI agent powered by pi-tui + pi-agent-core.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Layout:
|
|
6
|
+
* [Header overlay] — thesis id, confidence, model
|
|
7
|
+
* [Spacer] — room for header
|
|
8
|
+
* [Chat container] — messages (user, assistant, tool, system)
|
|
9
|
+
* [Editor] — multi-line input with slash command autocomplete
|
|
10
|
+
* [Spacer] — room for footer
|
|
11
|
+
* [Footer overlay] — tokens, cost, tool count, /help hint
|
|
7
12
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* inject_signal — inject news/note/external signal
|
|
11
|
-
* trigger_evaluation — trigger deep eval (heavy model)
|
|
12
|
-
* scan_markets — search Kalshi markets
|
|
13
|
-
* list_theses — list all theses
|
|
14
|
-
* get_positions — Kalshi portfolio positions
|
|
13
|
+
* Slash commands (bypass LLM):
|
|
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
|
-
};
|
|
19
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
17
|
exports.agentCommand = agentCommand;
|
|
21
|
-
const readline_1 = __importDefault(require("readline"));
|
|
22
18
|
const client_js_1 = require("../client.js");
|
|
23
19
|
const kalshi_js_1 = require("../kalshi.js");
|
|
20
|
+
// ─── ANSI 24-bit color helpers (no chalk dependency) ─────────────────────────
|
|
21
|
+
const rgb = (r, g, b) => (s) => `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m`;
|
|
22
|
+
const bgRgb = (r, g, b) => (s) => `\x1b[48;2;${r};${g};${b}m${s}\x1b[49m`;
|
|
23
|
+
const bold = (s) => `\x1b[1m${s}\x1b[22m`;
|
|
24
|
+
const dim = (s) => `\x1b[2m${s}\x1b[22m`;
|
|
25
|
+
const italic = (s) => `\x1b[3m${s}\x1b[23m`;
|
|
26
|
+
const underline = (s) => `\x1b[4m${s}\x1b[24m`;
|
|
27
|
+
const strikethrough = (s) => `\x1b[9m${s}\x1b[29m`;
|
|
28
|
+
const C = {
|
|
29
|
+
emerald: rgb(16, 185, 129), // #10b981
|
|
30
|
+
zinc200: rgb(228, 228, 231), // #e4e4e7
|
|
31
|
+
zinc400: rgb(161, 161, 170), // #a1a1aa
|
|
32
|
+
zinc600: rgb(82, 82, 91), // #52525b
|
|
33
|
+
zinc800: rgb(39, 39, 42), // #27272a
|
|
34
|
+
red: rgb(239, 68, 68), // #ef4444
|
|
35
|
+
amber: rgb(245, 158, 11), // #f59e0b
|
|
36
|
+
white: rgb(255, 255, 255),
|
|
37
|
+
bgZinc900: bgRgb(24, 24, 27), // #18181b
|
|
38
|
+
bgZinc800: bgRgb(39, 39, 42), // #27272a
|
|
39
|
+
};
|
|
40
|
+
// ─── Custom components ───────────────────────────────────────────────────────
|
|
41
|
+
/** Mutable single-line component (TruncatedText is immutable) */
|
|
42
|
+
function createMutableLine(piTui) {
|
|
43
|
+
const { truncateToWidth, visibleWidth } = piTui;
|
|
44
|
+
return class MutableLine {
|
|
45
|
+
text;
|
|
46
|
+
cachedWidth;
|
|
47
|
+
cachedLines;
|
|
48
|
+
constructor(text) {
|
|
49
|
+
this.text = text;
|
|
50
|
+
}
|
|
51
|
+
setText(text) {
|
|
52
|
+
this.text = text;
|
|
53
|
+
this.cachedWidth = undefined;
|
|
54
|
+
this.cachedLines = undefined;
|
|
55
|
+
}
|
|
56
|
+
invalidate() {
|
|
57
|
+
this.cachedWidth = undefined;
|
|
58
|
+
this.cachedLines = undefined;
|
|
59
|
+
}
|
|
60
|
+
render(width) {
|
|
61
|
+
if (this.cachedLines && this.cachedWidth === width)
|
|
62
|
+
return this.cachedLines;
|
|
63
|
+
this.cachedWidth = width;
|
|
64
|
+
this.cachedLines = [truncateToWidth(this.text, width)];
|
|
65
|
+
return this.cachedLines;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Header bar: [SF Agent — {id}] [{confidence}%] [{model}] */
|
|
70
|
+
function createHeaderBar(piTui) {
|
|
71
|
+
const { truncateToWidth, visibleWidth } = piTui;
|
|
72
|
+
return class HeaderBar {
|
|
73
|
+
left;
|
|
74
|
+
center;
|
|
75
|
+
right;
|
|
76
|
+
cachedWidth;
|
|
77
|
+
cachedLines;
|
|
78
|
+
constructor(left, center, right) {
|
|
79
|
+
this.left = left;
|
|
80
|
+
this.center = center;
|
|
81
|
+
this.right = right;
|
|
82
|
+
}
|
|
83
|
+
update(left, center, right) {
|
|
84
|
+
if (left !== undefined)
|
|
85
|
+
this.left = left;
|
|
86
|
+
if (center !== undefined)
|
|
87
|
+
this.center = center;
|
|
88
|
+
if (right !== undefined)
|
|
89
|
+
this.right = right;
|
|
90
|
+
this.cachedWidth = undefined;
|
|
91
|
+
this.cachedLines = undefined;
|
|
92
|
+
}
|
|
93
|
+
invalidate() {
|
|
94
|
+
this.cachedWidth = undefined;
|
|
95
|
+
this.cachedLines = undefined;
|
|
96
|
+
}
|
|
97
|
+
render(width) {
|
|
98
|
+
if (this.cachedLines && this.cachedWidth === width)
|
|
99
|
+
return this.cachedLines;
|
|
100
|
+
this.cachedWidth = width;
|
|
101
|
+
const l = this.left;
|
|
102
|
+
const c = this.center;
|
|
103
|
+
const r = this.right;
|
|
104
|
+
const lw = visibleWidth(l);
|
|
105
|
+
const cw = visibleWidth(c);
|
|
106
|
+
const rw = visibleWidth(r);
|
|
107
|
+
const totalContent = lw + cw + rw;
|
|
108
|
+
const totalPad = Math.max(0, width - totalContent);
|
|
109
|
+
const leftPad = Math.max(1, Math.floor(totalPad / 2));
|
|
110
|
+
const rightPad = Math.max(1, totalPad - leftPad);
|
|
111
|
+
let line = l + ' '.repeat(leftPad) + c + ' '.repeat(rightPad) + r;
|
|
112
|
+
line = C.bgZinc900(truncateToWidth(line, width, ''));
|
|
113
|
+
// Pad to full width for background
|
|
114
|
+
const lineVw = visibleWidth(line);
|
|
115
|
+
if (lineVw < width) {
|
|
116
|
+
line = line + C.bgZinc900(' '.repeat(width - lineVw));
|
|
117
|
+
}
|
|
118
|
+
this.cachedLines = [line];
|
|
119
|
+
return this.cachedLines;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Footer bar: [tokens: N | cost: $N | tools: N] [/help] */
|
|
124
|
+
function createFooterBar(piTui) {
|
|
125
|
+
const { truncateToWidth, visibleWidth } = piTui;
|
|
126
|
+
return class FooterBar {
|
|
127
|
+
tokens = 0;
|
|
128
|
+
cost = 0;
|
|
129
|
+
toolCount = 0;
|
|
130
|
+
cachedWidth;
|
|
131
|
+
cachedLines;
|
|
132
|
+
invalidate() {
|
|
133
|
+
this.cachedWidth = undefined;
|
|
134
|
+
this.cachedLines = undefined;
|
|
135
|
+
}
|
|
136
|
+
update() {
|
|
137
|
+
this.cachedWidth = undefined;
|
|
138
|
+
this.cachedLines = undefined;
|
|
139
|
+
}
|
|
140
|
+
render(width) {
|
|
141
|
+
if (this.cachedLines && this.cachedWidth === width)
|
|
142
|
+
return this.cachedLines;
|
|
143
|
+
this.cachedWidth = width;
|
|
144
|
+
const tokStr = this.tokens >= 1000
|
|
145
|
+
? `${(this.tokens / 1000).toFixed(1)}k`
|
|
146
|
+
: `${this.tokens}`;
|
|
147
|
+
const leftText = C.zinc600(`tokens: ${tokStr} cost: $${this.cost.toFixed(3)} tools: ${this.toolCount}`);
|
|
148
|
+
const rightText = C.zinc600('/help');
|
|
149
|
+
const lw = visibleWidth(leftText);
|
|
150
|
+
const rw = visibleWidth(rightText);
|
|
151
|
+
const gap = Math.max(1, width - lw - rw);
|
|
152
|
+
let line = leftText + ' '.repeat(gap) + rightText;
|
|
153
|
+
line = C.bgZinc900(truncateToWidth(line, width, ''));
|
|
154
|
+
const lineVw = visibleWidth(line);
|
|
155
|
+
if (lineVw < width) {
|
|
156
|
+
line = line + C.bgZinc900(' '.repeat(width - lineVw));
|
|
157
|
+
}
|
|
158
|
+
this.cachedLines = [line];
|
|
159
|
+
return this.cachedLines;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// ─── Formatted renderers ─────────────────────────────────────────────────────
|
|
164
|
+
function renderCausalTree(context, piTui) {
|
|
165
|
+
const tree = context.causalTree;
|
|
166
|
+
if (!tree?.nodes?.length)
|
|
167
|
+
return C.zinc600(' No causal tree data');
|
|
168
|
+
const lines = [];
|
|
169
|
+
for (const node of tree.nodes) {
|
|
170
|
+
const id = node.id || '';
|
|
171
|
+
const label = node.label || node.description || '';
|
|
172
|
+
const prob = typeof node.probability === 'number'
|
|
173
|
+
? Math.round(node.probability * 100)
|
|
174
|
+
: (typeof node.impliedProbability === 'number' ? Math.round(node.impliedProbability * 100) : null);
|
|
175
|
+
const depth = (id.match(/\./g) || []).length;
|
|
176
|
+
const indent = ' '.repeat(depth + 1);
|
|
177
|
+
if (prob !== null) {
|
|
178
|
+
// Progress bar: 10 chars
|
|
179
|
+
const filled = Math.round(prob / 10);
|
|
180
|
+
const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(10 - filled);
|
|
181
|
+
const probColor = prob >= 70 ? C.emerald : prob >= 40 ? C.amber : C.red;
|
|
182
|
+
// Dots to pad between label and percentage
|
|
183
|
+
const labelPart = `${indent}${C.zinc600(id)} ${C.zinc400(label)} `;
|
|
184
|
+
const probPart = ` ${probColor(`${prob}%`)} ${probColor(bar)}`;
|
|
185
|
+
lines.push(labelPart + probPart);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
lines.push(`${indent}${C.zinc600(id)} ${C.zinc400(label)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return lines.join('\n');
|
|
192
|
+
}
|
|
193
|
+
function renderEdges(context, piTui) {
|
|
194
|
+
const edges = context.edges;
|
|
195
|
+
if (!edges?.length)
|
|
196
|
+
return C.zinc600(' No edge data');
|
|
197
|
+
const positions = context._positions || [];
|
|
198
|
+
const lines = [];
|
|
199
|
+
for (const e of edges) {
|
|
200
|
+
const name = (e.marketTitle || e.ticker || '').slice(0, 18).padEnd(18);
|
|
201
|
+
const market = typeof e.marketPrice === 'number' ? `${e.marketPrice}\u00A2` : '?';
|
|
202
|
+
const thesis = typeof e.thesisImpliedPrice === 'number' ? `${e.thesisImpliedPrice}\u00A2` : '?';
|
|
203
|
+
const edge = typeof e.edgeSize === 'number' ? (e.edgeSize > 0 ? `+${e.edgeSize}` : `${e.edgeSize}`) : '?';
|
|
204
|
+
const spread = typeof e.spread === 'number' ? `${e.spread}\u00A2` : '?';
|
|
205
|
+
const liq = e.liquidityScore || 'low';
|
|
206
|
+
const liqBars = liq === 'high' ? '\u25A0\u25A0\u25A0' : liq === 'medium' ? '\u25A0\u25A0 ' : '\u25A0 ';
|
|
207
|
+
const liqColor = liq === 'high' ? C.emerald : liq === 'medium' ? C.amber : C.red;
|
|
208
|
+
// Check if we have a position on this edge
|
|
209
|
+
const pos = positions.find((p) => p.ticker === e.ticker);
|
|
210
|
+
let posStr = C.zinc600('\u2014');
|
|
211
|
+
if (pos) {
|
|
212
|
+
const side = pos.side?.toUpperCase() || 'YES';
|
|
213
|
+
const pnl = typeof pos.unrealized_pnl === 'number'
|
|
214
|
+
? (pos.unrealized_pnl >= 0 ? C.emerald(`+$${(pos.unrealized_pnl / 100).toFixed(0)}`) : C.red(`-$${(Math.abs(pos.unrealized_pnl) / 100).toFixed(0)}`))
|
|
215
|
+
: '';
|
|
216
|
+
posStr = C.emerald(`${side} (${pos.quantity}@${pos.average_price_paid}\u00A2 ${pnl})`);
|
|
217
|
+
}
|
|
218
|
+
lines.push(` ${C.zinc200(name)} ${C.zinc400(market)} \u2192 ${C.zinc400(thesis)} edge ${edge.includes('+') ? C.emerald(edge) : C.red(edge)} spread ${C.zinc600(spread)} ${liqColor(liqBars)} ${liqColor(liq.padEnd(4))} ${posStr}`);
|
|
219
|
+
}
|
|
220
|
+
return lines.join('\n');
|
|
221
|
+
}
|
|
222
|
+
function renderPositions(positions) {
|
|
223
|
+
if (!positions?.length)
|
|
224
|
+
return C.zinc600(' No positions');
|
|
225
|
+
const lines = [];
|
|
226
|
+
let totalPnl = 0;
|
|
227
|
+
for (const p of positions) {
|
|
228
|
+
const ticker = (p.ticker || '').slice(0, 18).padEnd(18);
|
|
229
|
+
const side = (p.side || 'yes').toUpperCase().padEnd(3);
|
|
230
|
+
const qty = String(p.quantity || 0);
|
|
231
|
+
const avg = `${p.average_price_paid || 0}\u00A2`;
|
|
232
|
+
const now = typeof p.current_value === 'number' && p.current_value > 0
|
|
233
|
+
? `${p.current_value}\u00A2`
|
|
234
|
+
: '?\u00A2';
|
|
235
|
+
const pnlCents = p.unrealized_pnl || 0;
|
|
236
|
+
totalPnl += pnlCents;
|
|
237
|
+
const pnlDollars = (pnlCents / 100).toFixed(2);
|
|
238
|
+
const pnlStr = pnlCents >= 0
|
|
239
|
+
? C.emerald(`+$${pnlDollars}`)
|
|
240
|
+
: C.red(`-$${Math.abs(parseFloat(pnlDollars)).toFixed(2)}`);
|
|
241
|
+
const arrow = pnlCents >= 0 ? C.emerald('\u25B2') : C.red('\u25BC');
|
|
242
|
+
lines.push(` ${C.zinc200(ticker)} ${C.zinc400(side)} ${C.zinc400(qty)} @ ${C.zinc400(avg)} now ${C.zinc200(now)} ${pnlStr} ${arrow}`);
|
|
243
|
+
}
|
|
244
|
+
const totalDollars = (totalPnl / 100).toFixed(2);
|
|
245
|
+
lines.push(C.zinc600(' ' + '\u2500'.repeat(40)));
|
|
246
|
+
lines.push(totalPnl >= 0
|
|
247
|
+
? ` Total P&L: ${C.emerald(bold(`+$${totalDollars}`))}`
|
|
248
|
+
: ` Total P&L: ${C.red(bold(`-$${Math.abs(parseFloat(totalDollars)).toFixed(2)}`))}`);
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
// ─── Main command ────────────────────────────────────────────────────────────
|
|
24
252
|
async function agentCommand(thesisId, opts) {
|
|
25
|
-
// ── Dynamic imports
|
|
253
|
+
// ── Dynamic imports (all ESM-only packages) ────────────────────────────────
|
|
254
|
+
const piTui = await import('@mariozechner/pi-tui');
|
|
26
255
|
const piAi = await import('@mariozechner/pi-ai');
|
|
27
256
|
const piAgent = await import('@mariozechner/pi-agent-core');
|
|
257
|
+
const { TUI, ProcessTerminal, Container, Text, Markdown, Editor, Loader, Spacer, CombinedAutocompleteProvider, truncateToWidth, visibleWidth, } = piTui;
|
|
28
258
|
const { getModel, streamSimple, Type } = piAi;
|
|
29
259
|
const { Agent } = piAgent;
|
|
260
|
+
// ── Component class factories (need piTui ref) ─────────────────────────────
|
|
261
|
+
const MutableLine = createMutableLine(piTui);
|
|
262
|
+
const HeaderBar = createHeaderBar(piTui);
|
|
263
|
+
const FooterBar = createFooterBar(piTui);
|
|
30
264
|
// ── Validate API keys ──────────────────────────────────────────────────────
|
|
31
265
|
const openrouterKey = opts?.modelKey || process.env.OPENROUTER_API_KEY;
|
|
32
266
|
if (!openrouterKey) {
|
|
@@ -47,8 +281,124 @@ async function agentCommand(thesisId, opts) {
|
|
|
47
281
|
resolvedThesisId = active.id;
|
|
48
282
|
}
|
|
49
283
|
// ── Fetch initial context ──────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
// ──
|
|
284
|
+
let latestContext = await sfClient.getContext(resolvedThesisId);
|
|
285
|
+
// ── Model setup ────────────────────────────────────────────────────────────
|
|
286
|
+
const rawModelName = opts?.model || 'anthropic/claude-sonnet-4-20250514';
|
|
287
|
+
let currentModelName = rawModelName.replace(/^openrouter\//, '');
|
|
288
|
+
function resolveModel(name) {
|
|
289
|
+
try {
|
|
290
|
+
return getModel('openrouter', name);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return {
|
|
294
|
+
modelId: name,
|
|
295
|
+
provider: 'openrouter',
|
|
296
|
+
api: 'openai-completions',
|
|
297
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
298
|
+
id: name,
|
|
299
|
+
name: name,
|
|
300
|
+
inputPrice: 0,
|
|
301
|
+
outputPrice: 0,
|
|
302
|
+
contextWindow: 200000,
|
|
303
|
+
supportsImages: true,
|
|
304
|
+
supportsTools: true,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
let model = resolveModel(currentModelName);
|
|
309
|
+
// ── Tracking state ─────────────────────────────────────────────────────────
|
|
310
|
+
let totalTokens = 0;
|
|
311
|
+
let totalCost = 0;
|
|
312
|
+
let totalToolCalls = 0;
|
|
313
|
+
let isProcessing = false;
|
|
314
|
+
// Cache for positions (fetched by /pos or get_positions tool)
|
|
315
|
+
let cachedPositions = null;
|
|
316
|
+
// ── Setup TUI ──────────────────────────────────────────────────────────────
|
|
317
|
+
const terminal = new ProcessTerminal();
|
|
318
|
+
const tui = new TUI(terminal);
|
|
319
|
+
// Markdown theme for assistant messages
|
|
320
|
+
const mdTheme = {
|
|
321
|
+
heading: (s) => C.zinc200(bold(s)),
|
|
322
|
+
link: (s) => C.emerald(s),
|
|
323
|
+
linkUrl: (s) => C.zinc600(s),
|
|
324
|
+
code: (s) => C.zinc200(s),
|
|
325
|
+
codeBlock: (s) => C.zinc400(s),
|
|
326
|
+
codeBlockBorder: (s) => C.zinc600(s),
|
|
327
|
+
quote: (s) => C.zinc400(s),
|
|
328
|
+
quoteBorder: (s) => C.zinc600(s),
|
|
329
|
+
hr: (s) => C.zinc600(s),
|
|
330
|
+
listBullet: (s) => C.emerald(s),
|
|
331
|
+
bold: (s) => bold(s),
|
|
332
|
+
italic: (s) => italic(s),
|
|
333
|
+
strikethrough: (s) => strikethrough(s),
|
|
334
|
+
underline: (s) => underline(s),
|
|
335
|
+
};
|
|
336
|
+
const mdDefaultStyle = {
|
|
337
|
+
color: (s) => C.zinc400(s),
|
|
338
|
+
};
|
|
339
|
+
// Editor theme
|
|
340
|
+
const editorTheme = {
|
|
341
|
+
borderColor: (s) => C.zinc800(s),
|
|
342
|
+
selectList: {
|
|
343
|
+
selectedPrefix: (s) => C.emerald(s),
|
|
344
|
+
selectedText: (s) => C.zinc200(s),
|
|
345
|
+
description: (s) => C.zinc600(s),
|
|
346
|
+
scrollInfo: (s) => C.zinc600(s),
|
|
347
|
+
noMatch: (s) => C.zinc600(s),
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
// ── Build components ───────────────────────────────────────────────────────
|
|
351
|
+
const shortId = (resolvedThesisId || '').slice(0, 8);
|
|
352
|
+
const confidencePct = typeof latestContext.confidence === 'number'
|
|
353
|
+
? Math.round(latestContext.confidence * 100)
|
|
354
|
+
: (typeof latestContext.confidence === 'string' ? parseInt(latestContext.confidence) : 0);
|
|
355
|
+
const headerBar = new HeaderBar(C.emerald(bold('SF Agent')) + C.zinc600(` \u2014 ${shortId}`), confidencePct > 0 ? C.zinc200(`${confidencePct}%`) : '', C.zinc600(currentModelName.split('/').pop() || currentModelName));
|
|
356
|
+
const footerBar = new FooterBar();
|
|
357
|
+
const topSpacer = new Spacer(1);
|
|
358
|
+
const bottomSpacer = new Spacer(1);
|
|
359
|
+
const chatContainer = new Container();
|
|
360
|
+
const editor = new Editor(tui, editorTheme, { paddingX: 1 });
|
|
361
|
+
// Slash command autocomplete
|
|
362
|
+
const autocompleteProvider = new CombinedAutocompleteProvider([
|
|
363
|
+
{ name: 'help', description: 'Show available commands' },
|
|
364
|
+
{ name: 'tree', description: 'Display causal tree' },
|
|
365
|
+
{ name: 'edges', description: 'Display edge/spread table' },
|
|
366
|
+
{ name: 'pos', description: 'Display Kalshi positions' },
|
|
367
|
+
{ name: 'eval', description: 'Trigger deep evaluation' },
|
|
368
|
+
{ name: 'model', description: 'Switch model (e.g. /model anthropic/claude-sonnet-4)' },
|
|
369
|
+
{ name: 'clear', description: 'Clear chat' },
|
|
370
|
+
{ name: 'exit', description: 'Exit agent' },
|
|
371
|
+
], process.cwd());
|
|
372
|
+
editor.setAutocompleteProvider(autocompleteProvider);
|
|
373
|
+
// Assemble TUI tree
|
|
374
|
+
tui.addChild(topSpacer);
|
|
375
|
+
tui.addChild(chatContainer);
|
|
376
|
+
tui.addChild(editor);
|
|
377
|
+
tui.addChild(bottomSpacer);
|
|
378
|
+
// Focus on editor
|
|
379
|
+
tui.setFocus(editor);
|
|
380
|
+
// ── Overlays (pinned header + footer) ──────────────────────────────────────
|
|
381
|
+
const headerOverlay = tui.showOverlay(headerBar, {
|
|
382
|
+
row: 0,
|
|
383
|
+
col: 0,
|
|
384
|
+
width: '100%',
|
|
385
|
+
nonCapturing: true,
|
|
386
|
+
});
|
|
387
|
+
const footerOverlay = tui.showOverlay(footerBar, {
|
|
388
|
+
anchor: 'bottom-left',
|
|
389
|
+
width: '100%',
|
|
390
|
+
nonCapturing: true,
|
|
391
|
+
});
|
|
392
|
+
// ── Helper: add system text to chat ────────────────────────────────────────
|
|
393
|
+
function addSystemText(content) {
|
|
394
|
+
const text = new Text(content, 1, 0);
|
|
395
|
+
chatContainer.addChild(text);
|
|
396
|
+
tui.requestRender();
|
|
397
|
+
}
|
|
398
|
+
function addSpacer() {
|
|
399
|
+
chatContainer.addChild(new Spacer(1));
|
|
400
|
+
}
|
|
401
|
+
// ── Define agent tools (same as before) ────────────────────────────────────
|
|
52
402
|
const thesisIdParam = Type.Object({
|
|
53
403
|
thesisId: Type.String({ description: 'Thesis ID (short or full UUID)' }),
|
|
54
404
|
});
|
|
@@ -71,6 +421,13 @@ async function agentCommand(thesisId, opts) {
|
|
|
71
421
|
parameters: thesisIdParam,
|
|
72
422
|
execute: async (_toolCallId, params) => {
|
|
73
423
|
const ctx = await sfClient.getContext(params.thesisId);
|
|
424
|
+
latestContext = ctx;
|
|
425
|
+
// Update header with new confidence
|
|
426
|
+
const conf = typeof ctx.confidence === 'number'
|
|
427
|
+
? Math.round(ctx.confidence * 100)
|
|
428
|
+
: 0;
|
|
429
|
+
headerBar.update(undefined, conf > 0 ? C.zinc200(`${conf}%`) : '', undefined);
|
|
430
|
+
tui.requestRender();
|
|
74
431
|
return {
|
|
75
432
|
content: [{ type: 'text', text: JSON.stringify(ctx, null, 2) }],
|
|
76
433
|
details: {},
|
|
@@ -150,22 +507,16 @@ async function agentCommand(thesisId, opts) {
|
|
|
150
507
|
{
|
|
151
508
|
name: 'get_positions',
|
|
152
509
|
label: 'Get Positions',
|
|
153
|
-
description: 'Get Kalshi exchange positions with live prices and PnL
|
|
510
|
+
description: 'Get Kalshi exchange positions with live prices and PnL',
|
|
154
511
|
parameters: emptyParams,
|
|
155
512
|
execute: async () => {
|
|
156
513
|
const positions = await (0, kalshi_js_1.getPositions)();
|
|
157
514
|
if (!positions) {
|
|
158
515
|
return {
|
|
159
|
-
content: [
|
|
160
|
-
{
|
|
161
|
-
type: 'text',
|
|
162
|
-
text: 'Kalshi not configured. Set KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY_PATH environment variables.',
|
|
163
|
-
},
|
|
164
|
-
],
|
|
516
|
+
content: [{ type: 'text', text: 'Kalshi not configured. Set KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY_PATH.' }],
|
|
165
517
|
details: {},
|
|
166
518
|
};
|
|
167
519
|
}
|
|
168
|
-
// Enrich with live prices (same logic as sf positions command)
|
|
169
520
|
for (const pos of positions) {
|
|
170
521
|
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
171
522
|
if (livePrice !== null) {
|
|
@@ -173,6 +524,7 @@ async function agentCommand(thesisId, opts) {
|
|
|
173
524
|
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
174
525
|
}
|
|
175
526
|
}
|
|
527
|
+
cachedPositions = positions;
|
|
176
528
|
return {
|
|
177
529
|
content: [{ type: 'text', text: JSON.stringify(positions, null, 2) }],
|
|
178
530
|
details: {},
|
|
@@ -181,47 +533,16 @@ async function agentCommand(thesisId, opts) {
|
|
|
181
533
|
},
|
|
182
534
|
];
|
|
183
535
|
// ── System prompt ──────────────────────────────────────────────────────────
|
|
184
|
-
const confidencePct = typeof context.confidence === 'number'
|
|
185
|
-
? Math.round(context.confidence * 100)
|
|
186
|
-
: context.confidence;
|
|
187
536
|
const systemPrompt = `You are a SimpleFunctions prediction market trading assistant.
|
|
188
537
|
|
|
189
|
-
Current thesis: ${
|
|
538
|
+
Current thesis: ${latestContext.thesis || latestContext.rawThesis || 'N/A'}
|
|
190
539
|
Confidence: ${confidencePct}%
|
|
191
|
-
Status: ${
|
|
192
|
-
Thesis ID: ${
|
|
540
|
+
Status: ${latestContext.status}
|
|
541
|
+
Thesis ID: ${latestContext.thesisId || resolvedThesisId}
|
|
193
542
|
|
|
194
543
|
You have six tools available. Use them when you need real-time data. Answer directly when you don't.
|
|
195
544
|
Be concise. Use Chinese if the user writes in Chinese, English if they write in English.
|
|
196
545
|
Do NOT make up data. Always call tools to get current state.`;
|
|
197
|
-
// ── Configure model via OpenRouter ─────────────────────────────────────────
|
|
198
|
-
const rawModelName = opts?.model || 'anthropic/claude-sonnet-4-20250514';
|
|
199
|
-
// Strip 'openrouter/' prefix if user passed it (provider is already openrouter)
|
|
200
|
-
const modelName = rawModelName.replace(/^openrouter\//, '');
|
|
201
|
-
// Try the registry first. If the model isn't registered, construct manually.
|
|
202
|
-
// OpenRouter accepts any model ID — the registry just may not have it yet.
|
|
203
|
-
let model;
|
|
204
|
-
try {
|
|
205
|
-
model = getModel('openrouter', modelName);
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
// Manual fallback: construct a Model object compatible with pi-ai's openai-completions API
|
|
209
|
-
// (OpenRouter uses OpenAI-compatible API)
|
|
210
|
-
model = {
|
|
211
|
-
modelId: modelName,
|
|
212
|
-
provider: 'openrouter',
|
|
213
|
-
api: 'openai-completions',
|
|
214
|
-
baseUrl: 'https://openrouter.ai/api/v1',
|
|
215
|
-
id: modelName,
|
|
216
|
-
name: modelName,
|
|
217
|
-
inputPrice: 0,
|
|
218
|
-
outputPrice: 0,
|
|
219
|
-
contextWindow: 200000,
|
|
220
|
-
supportsImages: true,
|
|
221
|
-
supportsTools: true,
|
|
222
|
-
};
|
|
223
|
-
console.log(`\x1b[33mModel '${modelName}' not in registry, using direct OpenRouter API\x1b[0m`);
|
|
224
|
-
}
|
|
225
546
|
// ── Create Agent ───────────────────────────────────────────────────────────
|
|
226
547
|
const agent = new Agent({
|
|
227
548
|
initialState: {
|
|
@@ -237,54 +558,272 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
237
558
|
return undefined;
|
|
238
559
|
},
|
|
239
560
|
});
|
|
240
|
-
// ── Subscribe to
|
|
561
|
+
// ── Subscribe to agent events → update TUI ────────────────────────────────
|
|
562
|
+
let currentAssistantMd = null;
|
|
563
|
+
let currentAssistantText = '';
|
|
564
|
+
let currentLoader = null;
|
|
565
|
+
const toolStartTimes = new Map();
|
|
566
|
+
const toolLines = new Map();
|
|
241
567
|
agent.subscribe((event) => {
|
|
568
|
+
if (event.type === 'message_start') {
|
|
569
|
+
// Show loader while waiting for first text
|
|
570
|
+
currentAssistantText = '';
|
|
571
|
+
currentAssistantMd = null;
|
|
572
|
+
currentLoader = new Loader(tui, (s) => C.emerald(s), (s) => C.zinc600(s), 'thinking...');
|
|
573
|
+
currentLoader.start();
|
|
574
|
+
chatContainer.addChild(currentLoader);
|
|
575
|
+
tui.requestRender();
|
|
576
|
+
}
|
|
242
577
|
if (event.type === 'message_update') {
|
|
243
578
|
const e = event.assistantMessageEvent;
|
|
244
579
|
if (e.type === 'text_delta') {
|
|
245
|
-
|
|
580
|
+
// Remove loader on first text delta
|
|
581
|
+
if (currentLoader) {
|
|
582
|
+
currentLoader.stop();
|
|
583
|
+
chatContainer.removeChild(currentLoader);
|
|
584
|
+
currentLoader = null;
|
|
585
|
+
// Create markdown component for assistant response
|
|
586
|
+
currentAssistantMd = new Markdown('', 1, 0, mdTheme, mdDefaultStyle);
|
|
587
|
+
chatContainer.addChild(currentAssistantMd);
|
|
588
|
+
}
|
|
589
|
+
currentAssistantText += e.delta;
|
|
590
|
+
if (currentAssistantMd) {
|
|
591
|
+
currentAssistantMd.setText(currentAssistantText);
|
|
592
|
+
}
|
|
593
|
+
tui.requestRender();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (event.type === 'message_complete') {
|
|
597
|
+
// Clean up loader if still present (no text was generated)
|
|
598
|
+
if (currentLoader) {
|
|
599
|
+
currentLoader.stop();
|
|
600
|
+
chatContainer.removeChild(currentLoader);
|
|
601
|
+
currentLoader = null;
|
|
602
|
+
}
|
|
603
|
+
// Add spacer after message
|
|
604
|
+
addSpacer();
|
|
605
|
+
currentAssistantMd = null;
|
|
606
|
+
currentAssistantText = '';
|
|
607
|
+
isProcessing = false;
|
|
608
|
+
tui.requestRender();
|
|
609
|
+
// Update token/cost tracking from event if available
|
|
610
|
+
if (event.usage) {
|
|
611
|
+
totalTokens += (event.usage.inputTokens || 0) + (event.usage.outputTokens || 0);
|
|
612
|
+
totalCost += event.usage.totalCost || 0;
|
|
613
|
+
footerBar.tokens = totalTokens;
|
|
614
|
+
footerBar.cost = totalCost;
|
|
615
|
+
footerBar.update();
|
|
246
616
|
}
|
|
247
617
|
}
|
|
248
618
|
if (event.type === 'tool_execution_start') {
|
|
249
|
-
|
|
619
|
+
const toolLine = new MutableLine(C.zinc600(` \u26A1 ${event.toolName}...`));
|
|
620
|
+
toolStartTimes.set(event.toolCallId || event.toolName, Date.now());
|
|
621
|
+
toolLines.set(event.toolCallId || event.toolName, toolLine);
|
|
622
|
+
chatContainer.addChild(toolLine);
|
|
623
|
+
totalToolCalls++;
|
|
624
|
+
footerBar.toolCount = totalToolCalls;
|
|
625
|
+
footerBar.update();
|
|
626
|
+
tui.requestRender();
|
|
250
627
|
}
|
|
251
628
|
if (event.type === 'tool_execution_end') {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
629
|
+
const key = event.toolCallId || event.toolName;
|
|
630
|
+
const startTime = toolStartTimes.get(key);
|
|
631
|
+
const elapsed = startTime ? ((Date.now() - startTime) / 1000).toFixed(1) : '?';
|
|
632
|
+
const line = toolLines.get(key);
|
|
633
|
+
if (line) {
|
|
634
|
+
if (event.isError) {
|
|
635
|
+
line.setText(C.red(` \u2717 ${event.toolName} (${elapsed}s) error`));
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
line.setText(C.zinc600(` \u26A1 ${event.toolName}`) + C.emerald(` \u2713`) + C.zinc600(` (${elapsed}s)`));
|
|
639
|
+
}
|
|
257
640
|
}
|
|
641
|
+
toolStartTimes.delete(key);
|
|
642
|
+
toolLines.delete(key);
|
|
643
|
+
tui.requestRender();
|
|
258
644
|
}
|
|
259
645
|
});
|
|
260
|
-
// ──
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
646
|
+
// ── Slash command handlers ─────────────────────────────────────────────────
|
|
647
|
+
async function handleSlashCommand(cmd) {
|
|
648
|
+
const parts = cmd.trim().split(/\s+/);
|
|
649
|
+
const command = parts[0].toLowerCase();
|
|
650
|
+
switch (command) {
|
|
651
|
+
case '/help': {
|
|
652
|
+
addSpacer();
|
|
653
|
+
addSystemText(C.zinc200(bold('Commands')) + '\n' +
|
|
654
|
+
C.emerald('/help ') + C.zinc400(' Show this help') + '\n' +
|
|
655
|
+
C.emerald('/tree ') + C.zinc400(' Display causal tree') + '\n' +
|
|
656
|
+
C.emerald('/edges ') + C.zinc400(' Display edge/spread table') + '\n' +
|
|
657
|
+
C.emerald('/pos ') + C.zinc400(' Display Kalshi positions') + '\n' +
|
|
658
|
+
C.emerald('/eval ') + C.zinc400(' Trigger deep evaluation') + '\n' +
|
|
659
|
+
C.emerald('/model ') + C.zinc400(' Switch model (e.g. /model anthropic/claude-sonnet-4)') + '\n' +
|
|
660
|
+
C.emerald('/clear ') + C.zinc400(' Clear chat') + '\n' +
|
|
661
|
+
C.emerald('/exit ') + C.zinc400(' Exit agent'));
|
|
662
|
+
addSpacer();
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
case '/tree': {
|
|
666
|
+
addSpacer();
|
|
667
|
+
// Refresh context first
|
|
668
|
+
try {
|
|
669
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
670
|
+
addSystemText(C.zinc200(bold('Causal Tree')) + '\n' + renderCausalTree(latestContext, piTui));
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
674
|
+
}
|
|
675
|
+
addSpacer();
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
case '/edges': {
|
|
679
|
+
addSpacer();
|
|
680
|
+
try {
|
|
681
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
682
|
+
// Attach cached positions for display
|
|
683
|
+
if (cachedPositions) {
|
|
684
|
+
latestContext._positions = cachedPositions;
|
|
685
|
+
}
|
|
686
|
+
addSystemText(C.zinc200(bold('Edges')) + '\n' + renderEdges(latestContext, piTui));
|
|
687
|
+
}
|
|
688
|
+
catch (err) {
|
|
689
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
690
|
+
}
|
|
691
|
+
addSpacer();
|
|
692
|
+
return true;
|
|
274
693
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
694
|
+
case '/pos': {
|
|
695
|
+
addSpacer();
|
|
696
|
+
try {
|
|
697
|
+
const positions = await (0, kalshi_js_1.getPositions)();
|
|
698
|
+
if (!positions) {
|
|
699
|
+
addSystemText(C.zinc600('Kalshi not configured'));
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
for (const pos of positions) {
|
|
703
|
+
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
704
|
+
if (livePrice !== null) {
|
|
705
|
+
pos.current_value = livePrice;
|
|
706
|
+
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
cachedPositions = positions;
|
|
710
|
+
addSystemText(C.zinc200(bold('Positions')) + '\n' + renderPositions(positions));
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
714
|
+
}
|
|
715
|
+
addSpacer();
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
case '/eval': {
|
|
719
|
+
addSpacer();
|
|
720
|
+
addSystemText(C.zinc600('Triggering evaluation...'));
|
|
721
|
+
tui.requestRender();
|
|
722
|
+
try {
|
|
723
|
+
const result = await sfClient.evaluate(resolvedThesisId);
|
|
724
|
+
addSystemText(C.emerald('Evaluation complete') + '\n' + C.zinc400(JSON.stringify(result, null, 2)));
|
|
725
|
+
}
|
|
726
|
+
catch (err) {
|
|
727
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
728
|
+
}
|
|
729
|
+
addSpacer();
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
case '/model': {
|
|
733
|
+
const newModel = parts.slice(1).join(' ').trim();
|
|
734
|
+
if (!newModel) {
|
|
735
|
+
addSystemText(C.zinc400(`Current model: ${currentModelName}`));
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
addSpacer();
|
|
739
|
+
currentModelName = newModel.replace(/^openrouter\//, '');
|
|
740
|
+
model = resolveModel(currentModelName);
|
|
741
|
+
// Update agent model
|
|
742
|
+
agent.setModel(model);
|
|
743
|
+
headerBar.update(undefined, undefined, C.zinc600(currentModelName.split('/').pop() || currentModelName));
|
|
744
|
+
addSystemText(C.emerald(`Model switched to ${currentModelName}`));
|
|
745
|
+
addSpacer();
|
|
746
|
+
tui.requestRender();
|
|
747
|
+
return true;
|
|
278
748
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
749
|
+
case '/clear': {
|
|
750
|
+
chatContainer.clear();
|
|
751
|
+
tui.requestRender();
|
|
752
|
+
return true;
|
|
282
753
|
}
|
|
283
|
-
|
|
284
|
-
|
|
754
|
+
case '/exit':
|
|
755
|
+
case '/quit': {
|
|
756
|
+
cleanup();
|
|
757
|
+
return true;
|
|
285
758
|
}
|
|
286
|
-
|
|
287
|
-
|
|
759
|
+
default:
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// ── Editor submit handler ──────────────────────────────────────────────────
|
|
764
|
+
editor.onSubmit = async (input) => {
|
|
765
|
+
const trimmed = input.trim();
|
|
766
|
+
if (!trimmed)
|
|
767
|
+
return;
|
|
768
|
+
if (isProcessing)
|
|
769
|
+
return;
|
|
770
|
+
// Add to editor history
|
|
771
|
+
editor.addToHistory(trimmed);
|
|
772
|
+
// Check for slash commands
|
|
773
|
+
if (trimmed.startsWith('/')) {
|
|
774
|
+
const handled = await handleSlashCommand(trimmed);
|
|
775
|
+
if (handled)
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
// Regular message → send to agent
|
|
779
|
+
isProcessing = true;
|
|
780
|
+
// Add user message to chat
|
|
781
|
+
const userMsg = new Text(C.emerald(bold('>')) + ' ' + C.white(trimmed), 1, 0);
|
|
782
|
+
chatContainer.addChild(userMsg);
|
|
783
|
+
addSpacer();
|
|
784
|
+
tui.requestRender();
|
|
785
|
+
try {
|
|
786
|
+
await agent.prompt(trimmed);
|
|
787
|
+
}
|
|
788
|
+
catch (err) {
|
|
789
|
+
// Remove loader if present
|
|
790
|
+
if (currentLoader) {
|
|
791
|
+
currentLoader.stop();
|
|
792
|
+
chatContainer.removeChild(currentLoader);
|
|
793
|
+
currentLoader = null;
|
|
794
|
+
}
|
|
795
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
796
|
+
addSpacer();
|
|
797
|
+
isProcessing = false;
|
|
798
|
+
}
|
|
288
799
|
};
|
|
289
|
-
|
|
800
|
+
// ── Ctrl+C handler ─────────────────────────────────────────────────────────
|
|
801
|
+
function cleanup() {
|
|
802
|
+
if (currentLoader)
|
|
803
|
+
currentLoader.stop();
|
|
804
|
+
tui.stop();
|
|
805
|
+
process.exit(0);
|
|
806
|
+
}
|
|
807
|
+
// Listen for Ctrl+C at the TUI level
|
|
808
|
+
tui.addInputListener((data) => {
|
|
809
|
+
// Ctrl+C = \x03
|
|
810
|
+
if (data === '\x03') {
|
|
811
|
+
cleanup();
|
|
812
|
+
return { consume: true };
|
|
813
|
+
}
|
|
814
|
+
return undefined;
|
|
815
|
+
});
|
|
816
|
+
// Also handle SIGINT
|
|
817
|
+
process.on('SIGINT', cleanup);
|
|
818
|
+
process.on('SIGTERM', cleanup);
|
|
819
|
+
// ── Show initial welcome ───────────────────────────────────────────────────
|
|
820
|
+
const thesisText = latestContext.thesis || latestContext.rawThesis || 'N/A';
|
|
821
|
+
const truncatedThesis = thesisText.length > 120 ? thesisText.slice(0, 120) + '...' : thesisText;
|
|
822
|
+
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
823
|
+
C.zinc200(bold(truncatedThesis)) + '\n' +
|
|
824
|
+
C.zinc600(`${latestContext.status || 'active'} ${confidencePct > 0 ? confidencePct + '%' : ''} ${(latestContext.edges || []).length} edges`) + '\n' +
|
|
825
|
+
C.zinc600('\u2500'.repeat(50)));
|
|
826
|
+
addSpacer();
|
|
827
|
+
// ── Start TUI ──────────────────────────────────────────────────────────────
|
|
828
|
+
tui.start();
|
|
290
829
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfunctions/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "CLI for SimpleFunctions prediction market thesis agent",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sf": "./dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc",
|
|
9
|
+
"build": "node ./node_modules/typescript/bin/tsc",
|
|
10
10
|
"dev": "tsx src/index.ts",
|
|
11
11
|
"prepublishOnly": "npm run build"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@mariozechner/pi-agent-core": "^0.57.1",
|
|
15
15
|
"@mariozechner/pi-ai": "^0.57.1",
|
|
16
|
+
"@mariozechner/pi-tui": "^0.57.1",
|
|
16
17
|
"commander": "^12.0.0",
|
|
17
18
|
"kalshi-typescript": "^3.9.0"
|
|
18
19
|
},
|