@spfunctions/cli 0.1.5 → 0.1.7
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 +651 -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,269 @@
|
|
|
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
|
+
// Context API field names: market, marketId, thesisPrice, edge, orderbook.spread, orderbook.liquidityScore
|
|
201
|
+
const name = (e.market || e.marketId || '').slice(0, 18).padEnd(18);
|
|
202
|
+
const marketStr = typeof e.marketPrice === 'number' ? `${e.marketPrice}\u00A2` : '?';
|
|
203
|
+
const thesisStr = typeof e.thesisPrice === 'number' ? `${e.thesisPrice}\u00A2` : '?';
|
|
204
|
+
const edgeVal = typeof e.edge === 'number' ? (e.edge > 0 ? `+${e.edge}` : `${e.edge}`) : '?';
|
|
205
|
+
const ob = e.orderbook || {};
|
|
206
|
+
const spreadStr = typeof ob.spread === 'number' ? `${ob.spread}\u00A2` : '?';
|
|
207
|
+
const liq = ob.liquidityScore || 'low';
|
|
208
|
+
const liqBars = liq === 'high' ? '\u25A0\u25A0\u25A0' : liq === 'medium' ? '\u25A0\u25A0 ' : '\u25A0 ';
|
|
209
|
+
const liqColor = liq === 'high' ? C.emerald : liq === 'medium' ? C.amber : C.red;
|
|
210
|
+
// Check if we have a position on this edge (match by marketId prefix in ticker)
|
|
211
|
+
const pos = positions.find((p) => p.ticker === e.marketId ||
|
|
212
|
+
(e.marketId && p.ticker?.includes(e.marketId)));
|
|
213
|
+
let posStr = C.zinc600('\u2014');
|
|
214
|
+
if (pos) {
|
|
215
|
+
const side = pos.side?.toUpperCase() || 'YES';
|
|
216
|
+
const pnl = typeof pos.unrealized_pnl === 'number'
|
|
217
|
+
? (pos.unrealized_pnl >= 0 ? C.emerald(`+$${(pos.unrealized_pnl / 100).toFixed(0)}`) : C.red(`-$${(Math.abs(pos.unrealized_pnl) / 100).toFixed(0)}`))
|
|
218
|
+
: '';
|
|
219
|
+
posStr = C.emerald(`${side} (${pos.quantity}@${pos.average_price_paid}\u00A2 ${pnl})`);
|
|
220
|
+
}
|
|
221
|
+
lines.push(` ${C.zinc200(name)} ${C.zinc400(marketStr)} \u2192 ${C.zinc400(thesisStr)} edge ${edgeVal.includes('+') ? C.emerald(edgeVal) : C.red(edgeVal)} spread ${C.zinc600(spreadStr)} ${liqColor(liqBars)} ${liqColor(liq.padEnd(4))} ${posStr}`);
|
|
222
|
+
}
|
|
223
|
+
return lines.join('\n');
|
|
224
|
+
}
|
|
225
|
+
function renderPositions(positions) {
|
|
226
|
+
if (!positions?.length)
|
|
227
|
+
return C.zinc600(' No positions');
|
|
228
|
+
const lines = [];
|
|
229
|
+
let totalPnl = 0;
|
|
230
|
+
for (const p of positions) {
|
|
231
|
+
const ticker = (p.ticker || '').slice(0, 18).padEnd(18);
|
|
232
|
+
const side = (p.side || 'yes').toUpperCase().padEnd(3);
|
|
233
|
+
const qty = String(p.quantity || 0);
|
|
234
|
+
const avg = `${p.average_price_paid || 0}\u00A2`;
|
|
235
|
+
const now = typeof p.current_value === 'number' && p.current_value > 0
|
|
236
|
+
? `${p.current_value}\u00A2`
|
|
237
|
+
: '?\u00A2';
|
|
238
|
+
const pnlCents = p.unrealized_pnl || 0;
|
|
239
|
+
totalPnl += pnlCents;
|
|
240
|
+
const pnlDollars = (pnlCents / 100).toFixed(2);
|
|
241
|
+
const pnlStr = pnlCents >= 0
|
|
242
|
+
? C.emerald(`+$${pnlDollars}`)
|
|
243
|
+
: C.red(`-$${Math.abs(parseFloat(pnlDollars)).toFixed(2)}`);
|
|
244
|
+
const arrow = pnlCents >= 0 ? C.emerald('\u25B2') : C.red('\u25BC');
|
|
245
|
+
lines.push(` ${C.zinc200(ticker)} ${C.zinc400(side)} ${C.zinc400(qty)} @ ${C.zinc400(avg)} now ${C.zinc200(now)} ${pnlStr} ${arrow}`);
|
|
246
|
+
}
|
|
247
|
+
const totalDollars = (totalPnl / 100).toFixed(2);
|
|
248
|
+
lines.push(C.zinc600(' ' + '\u2500'.repeat(40)));
|
|
249
|
+
lines.push(totalPnl >= 0
|
|
250
|
+
? ` Total P&L: ${C.emerald(bold(`+$${totalDollars}`))}`
|
|
251
|
+
: ` Total P&L: ${C.red(bold(`-$${Math.abs(parseFloat(totalDollars)).toFixed(2)}`))}`);
|
|
252
|
+
return lines.join('\n');
|
|
253
|
+
}
|
|
254
|
+
// ─── Main command ────────────────────────────────────────────────────────────
|
|
24
255
|
async function agentCommand(thesisId, opts) {
|
|
25
|
-
// ── Dynamic imports
|
|
256
|
+
// ── Dynamic imports (all ESM-only packages) ────────────────────────────────
|
|
257
|
+
const piTui = await import('@mariozechner/pi-tui');
|
|
26
258
|
const piAi = await import('@mariozechner/pi-ai');
|
|
27
259
|
const piAgent = await import('@mariozechner/pi-agent-core');
|
|
260
|
+
const { TUI, ProcessTerminal, Container, Text, Markdown, Editor, Loader, Spacer, CombinedAutocompleteProvider, truncateToWidth, visibleWidth, } = piTui;
|
|
28
261
|
const { getModel, streamSimple, Type } = piAi;
|
|
29
262
|
const { Agent } = piAgent;
|
|
263
|
+
// ── Component class factories (need piTui ref) ─────────────────────────────
|
|
264
|
+
const MutableLine = createMutableLine(piTui);
|
|
265
|
+
const HeaderBar = createHeaderBar(piTui);
|
|
266
|
+
const FooterBar = createFooterBar(piTui);
|
|
30
267
|
// ── Validate API keys ──────────────────────────────────────────────────────
|
|
31
268
|
const openrouterKey = opts?.modelKey || process.env.OPENROUTER_API_KEY;
|
|
32
269
|
if (!openrouterKey) {
|
|
@@ -47,8 +284,124 @@ async function agentCommand(thesisId, opts) {
|
|
|
47
284
|
resolvedThesisId = active.id;
|
|
48
285
|
}
|
|
49
286
|
// ── Fetch initial context ──────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
// ──
|
|
287
|
+
let latestContext = await sfClient.getContext(resolvedThesisId);
|
|
288
|
+
// ── Model setup ────────────────────────────────────────────────────────────
|
|
289
|
+
const rawModelName = opts?.model || 'anthropic/claude-sonnet-4.6';
|
|
290
|
+
let currentModelName = rawModelName.replace(/^openrouter\//, '');
|
|
291
|
+
function resolveModel(name) {
|
|
292
|
+
try {
|
|
293
|
+
return getModel('openrouter', name);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return {
|
|
297
|
+
modelId: name,
|
|
298
|
+
provider: 'openrouter',
|
|
299
|
+
api: 'openai-completions',
|
|
300
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
301
|
+
id: name,
|
|
302
|
+
name: name,
|
|
303
|
+
inputPrice: 0,
|
|
304
|
+
outputPrice: 0,
|
|
305
|
+
contextWindow: 200000,
|
|
306
|
+
supportsImages: true,
|
|
307
|
+
supportsTools: true,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let model = resolveModel(currentModelName);
|
|
312
|
+
// ── Tracking state ─────────────────────────────────────────────────────────
|
|
313
|
+
let totalTokens = 0;
|
|
314
|
+
let totalCost = 0;
|
|
315
|
+
let totalToolCalls = 0;
|
|
316
|
+
let isProcessing = false;
|
|
317
|
+
// Cache for positions (fetched by /pos or get_positions tool)
|
|
318
|
+
let cachedPositions = null;
|
|
319
|
+
// ── Setup TUI ──────────────────────────────────────────────────────────────
|
|
320
|
+
const terminal = new ProcessTerminal();
|
|
321
|
+
const tui = new TUI(terminal);
|
|
322
|
+
// Markdown theme for assistant messages
|
|
323
|
+
const mdTheme = {
|
|
324
|
+
heading: (s) => C.zinc200(bold(s)),
|
|
325
|
+
link: (s) => C.emerald(s),
|
|
326
|
+
linkUrl: (s) => C.zinc600(s),
|
|
327
|
+
code: (s) => C.zinc200(s),
|
|
328
|
+
codeBlock: (s) => C.zinc400(s),
|
|
329
|
+
codeBlockBorder: (s) => C.zinc600(s),
|
|
330
|
+
quote: (s) => C.zinc400(s),
|
|
331
|
+
quoteBorder: (s) => C.zinc600(s),
|
|
332
|
+
hr: (s) => C.zinc600(s),
|
|
333
|
+
listBullet: (s) => C.emerald(s),
|
|
334
|
+
bold: (s) => bold(s),
|
|
335
|
+
italic: (s) => italic(s),
|
|
336
|
+
strikethrough: (s) => strikethrough(s),
|
|
337
|
+
underline: (s) => underline(s),
|
|
338
|
+
};
|
|
339
|
+
const mdDefaultStyle = {
|
|
340
|
+
color: (s) => C.zinc400(s),
|
|
341
|
+
};
|
|
342
|
+
// Editor theme
|
|
343
|
+
const editorTheme = {
|
|
344
|
+
borderColor: (s) => C.zinc800(s),
|
|
345
|
+
selectList: {
|
|
346
|
+
selectedPrefix: (s) => C.emerald(s),
|
|
347
|
+
selectedText: (s) => C.zinc200(s),
|
|
348
|
+
description: (s) => C.zinc600(s),
|
|
349
|
+
scrollInfo: (s) => C.zinc600(s),
|
|
350
|
+
noMatch: (s) => C.zinc600(s),
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
// ── Build components ───────────────────────────────────────────────────────
|
|
354
|
+
const shortId = (resolvedThesisId || '').slice(0, 8);
|
|
355
|
+
const confidencePct = typeof latestContext.confidence === 'number'
|
|
356
|
+
? Math.round(latestContext.confidence * 100)
|
|
357
|
+
: (typeof latestContext.confidence === 'string' ? parseInt(latestContext.confidence) : 0);
|
|
358
|
+
const headerBar = new HeaderBar(C.emerald(bold('SF Agent')) + C.zinc600(` \u2014 ${shortId}`), confidencePct > 0 ? C.zinc200(`${confidencePct}%`) : '', C.zinc600(currentModelName.split('/').pop() || currentModelName));
|
|
359
|
+
const footerBar = new FooterBar();
|
|
360
|
+
const topSpacer = new Spacer(1);
|
|
361
|
+
const bottomSpacer = new Spacer(1);
|
|
362
|
+
const chatContainer = new Container();
|
|
363
|
+
const editor = new Editor(tui, editorTheme, { paddingX: 1 });
|
|
364
|
+
// Slash command autocomplete
|
|
365
|
+
const autocompleteProvider = new CombinedAutocompleteProvider([
|
|
366
|
+
{ name: 'help', description: 'Show available commands' },
|
|
367
|
+
{ name: 'tree', description: 'Display causal tree' },
|
|
368
|
+
{ name: 'edges', description: 'Display edge/spread table' },
|
|
369
|
+
{ name: 'pos', description: 'Display Kalshi positions' },
|
|
370
|
+
{ name: 'eval', description: 'Trigger deep evaluation' },
|
|
371
|
+
{ name: 'model', description: 'Switch model (e.g. /model anthropic/claude-sonnet-4)' },
|
|
372
|
+
{ name: 'clear', description: 'Clear chat' },
|
|
373
|
+
{ name: 'exit', description: 'Exit agent' },
|
|
374
|
+
], process.cwd());
|
|
375
|
+
editor.setAutocompleteProvider(autocompleteProvider);
|
|
376
|
+
// Assemble TUI tree
|
|
377
|
+
tui.addChild(topSpacer);
|
|
378
|
+
tui.addChild(chatContainer);
|
|
379
|
+
tui.addChild(editor);
|
|
380
|
+
tui.addChild(bottomSpacer);
|
|
381
|
+
// Focus on editor
|
|
382
|
+
tui.setFocus(editor);
|
|
383
|
+
// ── Overlays (pinned header + footer) ──────────────────────────────────────
|
|
384
|
+
const headerOverlay = tui.showOverlay(headerBar, {
|
|
385
|
+
row: 0,
|
|
386
|
+
col: 0,
|
|
387
|
+
width: '100%',
|
|
388
|
+
nonCapturing: true,
|
|
389
|
+
});
|
|
390
|
+
const footerOverlay = tui.showOverlay(footerBar, {
|
|
391
|
+
anchor: 'bottom-left',
|
|
392
|
+
width: '100%',
|
|
393
|
+
nonCapturing: true,
|
|
394
|
+
});
|
|
395
|
+
// ── Helper: add system text to chat ────────────────────────────────────────
|
|
396
|
+
function addSystemText(content) {
|
|
397
|
+
const text = new Text(content, 1, 0);
|
|
398
|
+
chatContainer.addChild(text);
|
|
399
|
+
tui.requestRender();
|
|
400
|
+
}
|
|
401
|
+
function addSpacer() {
|
|
402
|
+
chatContainer.addChild(new Spacer(1));
|
|
403
|
+
}
|
|
404
|
+
// ── Define agent tools (same as before) ────────────────────────────────────
|
|
52
405
|
const thesisIdParam = Type.Object({
|
|
53
406
|
thesisId: Type.String({ description: 'Thesis ID (short or full UUID)' }),
|
|
54
407
|
});
|
|
@@ -71,6 +424,13 @@ async function agentCommand(thesisId, opts) {
|
|
|
71
424
|
parameters: thesisIdParam,
|
|
72
425
|
execute: async (_toolCallId, params) => {
|
|
73
426
|
const ctx = await sfClient.getContext(params.thesisId);
|
|
427
|
+
latestContext = ctx;
|
|
428
|
+
// Update header with new confidence
|
|
429
|
+
const conf = typeof ctx.confidence === 'number'
|
|
430
|
+
? Math.round(ctx.confidence * 100)
|
|
431
|
+
: 0;
|
|
432
|
+
headerBar.update(undefined, conf > 0 ? C.zinc200(`${conf}%`) : '', undefined);
|
|
433
|
+
tui.requestRender();
|
|
74
434
|
return {
|
|
75
435
|
content: [{ type: 'text', text: JSON.stringify(ctx, null, 2) }],
|
|
76
436
|
details: {},
|
|
@@ -150,22 +510,16 @@ async function agentCommand(thesisId, opts) {
|
|
|
150
510
|
{
|
|
151
511
|
name: 'get_positions',
|
|
152
512
|
label: 'Get Positions',
|
|
153
|
-
description: 'Get Kalshi exchange positions with live prices and PnL
|
|
513
|
+
description: 'Get Kalshi exchange positions with live prices and PnL',
|
|
154
514
|
parameters: emptyParams,
|
|
155
515
|
execute: async () => {
|
|
156
516
|
const positions = await (0, kalshi_js_1.getPositions)();
|
|
157
517
|
if (!positions) {
|
|
158
518
|
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
|
-
],
|
|
519
|
+
content: [{ type: 'text', text: 'Kalshi not configured. Set KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY_PATH.' }],
|
|
165
520
|
details: {},
|
|
166
521
|
};
|
|
167
522
|
}
|
|
168
|
-
// Enrich with live prices (same logic as sf positions command)
|
|
169
523
|
for (const pos of positions) {
|
|
170
524
|
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
171
525
|
if (livePrice !== null) {
|
|
@@ -173,6 +527,7 @@ async function agentCommand(thesisId, opts) {
|
|
|
173
527
|
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
174
528
|
}
|
|
175
529
|
}
|
|
530
|
+
cachedPositions = positions;
|
|
176
531
|
return {
|
|
177
532
|
content: [{ type: 'text', text: JSON.stringify(positions, null, 2) }],
|
|
178
533
|
details: {},
|
|
@@ -181,47 +536,16 @@ async function agentCommand(thesisId, opts) {
|
|
|
181
536
|
},
|
|
182
537
|
];
|
|
183
538
|
// ── System prompt ──────────────────────────────────────────────────────────
|
|
184
|
-
const confidencePct = typeof context.confidence === 'number'
|
|
185
|
-
? Math.round(context.confidence * 100)
|
|
186
|
-
: context.confidence;
|
|
187
539
|
const systemPrompt = `You are a SimpleFunctions prediction market trading assistant.
|
|
188
540
|
|
|
189
|
-
Current thesis: ${
|
|
541
|
+
Current thesis: ${latestContext.thesis || latestContext.rawThesis || 'N/A'}
|
|
190
542
|
Confidence: ${confidencePct}%
|
|
191
|
-
Status: ${
|
|
192
|
-
Thesis ID: ${
|
|
543
|
+
Status: ${latestContext.status}
|
|
544
|
+
Thesis ID: ${latestContext.thesisId || resolvedThesisId}
|
|
193
545
|
|
|
194
546
|
You have six tools available. Use them when you need real-time data. Answer directly when you don't.
|
|
195
547
|
Be concise. Use Chinese if the user writes in Chinese, English if they write in English.
|
|
196
548
|
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
549
|
// ── Create Agent ───────────────────────────────────────────────────────────
|
|
226
550
|
const agent = new Agent({
|
|
227
551
|
initialState: {
|
|
@@ -237,54 +561,289 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
237
561
|
return undefined;
|
|
238
562
|
},
|
|
239
563
|
});
|
|
240
|
-
// ── Subscribe to
|
|
564
|
+
// ── Subscribe to agent events → update TUI ────────────────────────────────
|
|
565
|
+
let currentAssistantMd = null;
|
|
566
|
+
let currentAssistantText = '';
|
|
567
|
+
let currentLoader = null;
|
|
568
|
+
const toolStartTimes = new Map();
|
|
569
|
+
const toolLines = new Map();
|
|
570
|
+
// Throttle renders during streaming to prevent flicker (max ~15fps)
|
|
571
|
+
let renderTimer = null;
|
|
572
|
+
function throttledRender() {
|
|
573
|
+
if (renderTimer)
|
|
574
|
+
return;
|
|
575
|
+
renderTimer = setTimeout(() => {
|
|
576
|
+
renderTimer = null;
|
|
577
|
+
tui.requestRender();
|
|
578
|
+
}, 66);
|
|
579
|
+
}
|
|
580
|
+
function flushRender() {
|
|
581
|
+
if (renderTimer) {
|
|
582
|
+
clearTimeout(renderTimer);
|
|
583
|
+
renderTimer = null;
|
|
584
|
+
}
|
|
585
|
+
tui.requestRender();
|
|
586
|
+
}
|
|
241
587
|
agent.subscribe((event) => {
|
|
588
|
+
if (event.type === 'message_start') {
|
|
589
|
+
// Show loader while waiting for first text
|
|
590
|
+
currentAssistantText = '';
|
|
591
|
+
currentAssistantMd = null;
|
|
592
|
+
currentLoader = new Loader(tui, (s) => C.emerald(s), (s) => C.zinc600(s), 'thinking...');
|
|
593
|
+
currentLoader.start();
|
|
594
|
+
chatContainer.addChild(currentLoader);
|
|
595
|
+
tui.requestRender();
|
|
596
|
+
}
|
|
242
597
|
if (event.type === 'message_update') {
|
|
243
598
|
const e = event.assistantMessageEvent;
|
|
244
599
|
if (e.type === 'text_delta') {
|
|
245
|
-
|
|
600
|
+
// Remove loader on first text delta
|
|
601
|
+
if (currentLoader) {
|
|
602
|
+
currentLoader.stop();
|
|
603
|
+
chatContainer.removeChild(currentLoader);
|
|
604
|
+
currentLoader = null;
|
|
605
|
+
// Create markdown component for assistant response
|
|
606
|
+
currentAssistantMd = new Markdown('', 1, 0, mdTheme, mdDefaultStyle);
|
|
607
|
+
chatContainer.addChild(currentAssistantMd);
|
|
608
|
+
}
|
|
609
|
+
currentAssistantText += e.delta;
|
|
610
|
+
if (currentAssistantMd) {
|
|
611
|
+
currentAssistantMd.setText(currentAssistantText);
|
|
612
|
+
}
|
|
613
|
+
// Throttled render to prevent flicker during fast token streaming
|
|
614
|
+
throttledRender();
|
|
246
615
|
}
|
|
247
616
|
}
|
|
617
|
+
if (event.type === 'message_end') {
|
|
618
|
+
// Clean up loader if still present (no text was generated)
|
|
619
|
+
if (currentLoader) {
|
|
620
|
+
currentLoader.stop();
|
|
621
|
+
chatContainer.removeChild(currentLoader);
|
|
622
|
+
currentLoader = null;
|
|
623
|
+
}
|
|
624
|
+
// Final render of the complete message
|
|
625
|
+
if (currentAssistantMd && currentAssistantText) {
|
|
626
|
+
currentAssistantMd.setText(currentAssistantText);
|
|
627
|
+
}
|
|
628
|
+
addSpacer();
|
|
629
|
+
currentAssistantMd = null;
|
|
630
|
+
currentAssistantText = '';
|
|
631
|
+
flushRender();
|
|
632
|
+
}
|
|
633
|
+
if (event.type === 'agent_end') {
|
|
634
|
+
// Agent turn fully complete — safe to accept new input
|
|
635
|
+
isProcessing = false;
|
|
636
|
+
flushRender();
|
|
637
|
+
}
|
|
248
638
|
if (event.type === 'tool_execution_start') {
|
|
249
|
-
|
|
639
|
+
const toolLine = new MutableLine(C.zinc600(` \u26A1 ${event.toolName}...`));
|
|
640
|
+
toolStartTimes.set(event.toolCallId || event.toolName, Date.now());
|
|
641
|
+
toolLines.set(event.toolCallId || event.toolName, toolLine);
|
|
642
|
+
chatContainer.addChild(toolLine);
|
|
643
|
+
totalToolCalls++;
|
|
644
|
+
footerBar.toolCount = totalToolCalls;
|
|
645
|
+
footerBar.update();
|
|
646
|
+
tui.requestRender();
|
|
250
647
|
}
|
|
251
648
|
if (event.type === 'tool_execution_end') {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
649
|
+
const key = event.toolCallId || event.toolName;
|
|
650
|
+
const startTime = toolStartTimes.get(key);
|
|
651
|
+
const elapsed = startTime ? ((Date.now() - startTime) / 1000).toFixed(1) : '?';
|
|
652
|
+
const line = toolLines.get(key);
|
|
653
|
+
if (line) {
|
|
654
|
+
if (event.isError) {
|
|
655
|
+
line.setText(C.red(` \u2717 ${event.toolName} (${elapsed}s) error`));
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
line.setText(C.zinc600(` \u26A1 ${event.toolName}`) + C.emerald(` \u2713`) + C.zinc600(` (${elapsed}s)`));
|
|
659
|
+
}
|
|
257
660
|
}
|
|
661
|
+
toolStartTimes.delete(key);
|
|
662
|
+
toolLines.delete(key);
|
|
663
|
+
tui.requestRender();
|
|
258
664
|
}
|
|
259
665
|
});
|
|
260
|
-
// ──
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
666
|
+
// ── Slash command handlers ─────────────────────────────────────────────────
|
|
667
|
+
async function handleSlashCommand(cmd) {
|
|
668
|
+
const parts = cmd.trim().split(/\s+/);
|
|
669
|
+
const command = parts[0].toLowerCase();
|
|
670
|
+
switch (command) {
|
|
671
|
+
case '/help': {
|
|
672
|
+
addSpacer();
|
|
673
|
+
addSystemText(C.zinc200(bold('Commands')) + '\n' +
|
|
674
|
+
C.emerald('/help ') + C.zinc400(' Show this help') + '\n' +
|
|
675
|
+
C.emerald('/tree ') + C.zinc400(' Display causal tree') + '\n' +
|
|
676
|
+
C.emerald('/edges ') + C.zinc400(' Display edge/spread table') + '\n' +
|
|
677
|
+
C.emerald('/pos ') + C.zinc400(' Display Kalshi positions') + '\n' +
|
|
678
|
+
C.emerald('/eval ') + C.zinc400(' Trigger deep evaluation') + '\n' +
|
|
679
|
+
C.emerald('/model ') + C.zinc400(' Switch model (e.g. /model anthropic/claude-sonnet-4)') + '\n' +
|
|
680
|
+
C.emerald('/clear ') + C.zinc400(' Clear chat') + '\n' +
|
|
681
|
+
C.emerald('/exit ') + C.zinc400(' Exit agent'));
|
|
682
|
+
addSpacer();
|
|
683
|
+
return true;
|
|
274
684
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
685
|
+
case '/tree': {
|
|
686
|
+
addSpacer();
|
|
687
|
+
// Refresh context first
|
|
688
|
+
try {
|
|
689
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
690
|
+
addSystemText(C.zinc200(bold('Causal Tree')) + '\n' + renderCausalTree(latestContext, piTui));
|
|
691
|
+
}
|
|
692
|
+
catch (err) {
|
|
693
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
694
|
+
}
|
|
695
|
+
addSpacer();
|
|
696
|
+
return true;
|
|
278
697
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
698
|
+
case '/edges': {
|
|
699
|
+
addSpacer();
|
|
700
|
+
try {
|
|
701
|
+
latestContext = await sfClient.getContext(resolvedThesisId);
|
|
702
|
+
// Attach cached positions for display
|
|
703
|
+
if (cachedPositions) {
|
|
704
|
+
latestContext._positions = cachedPositions;
|
|
705
|
+
}
|
|
706
|
+
addSystemText(C.zinc200(bold('Edges')) + '\n' + renderEdges(latestContext, piTui));
|
|
707
|
+
}
|
|
708
|
+
catch (err) {
|
|
709
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
710
|
+
}
|
|
711
|
+
addSpacer();
|
|
712
|
+
return true;
|
|
282
713
|
}
|
|
283
|
-
|
|
284
|
-
|
|
714
|
+
case '/pos': {
|
|
715
|
+
addSpacer();
|
|
716
|
+
try {
|
|
717
|
+
const positions = await (0, kalshi_js_1.getPositions)();
|
|
718
|
+
if (!positions) {
|
|
719
|
+
addSystemText(C.zinc600('Kalshi not configured'));
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
for (const pos of positions) {
|
|
723
|
+
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
724
|
+
if (livePrice !== null) {
|
|
725
|
+
pos.current_value = livePrice;
|
|
726
|
+
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
cachedPositions = positions;
|
|
730
|
+
addSystemText(C.zinc200(bold('Positions')) + '\n' + renderPositions(positions));
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
734
|
+
}
|
|
735
|
+
addSpacer();
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
case '/eval': {
|
|
739
|
+
addSpacer();
|
|
740
|
+
addSystemText(C.zinc600('Triggering evaluation...'));
|
|
741
|
+
tui.requestRender();
|
|
742
|
+
try {
|
|
743
|
+
const result = await sfClient.evaluate(resolvedThesisId);
|
|
744
|
+
addSystemText(C.emerald('Evaluation complete') + '\n' + C.zinc400(JSON.stringify(result, null, 2)));
|
|
745
|
+
}
|
|
746
|
+
catch (err) {
|
|
747
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
748
|
+
}
|
|
749
|
+
addSpacer();
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
case '/model': {
|
|
753
|
+
const newModel = parts.slice(1).join(' ').trim();
|
|
754
|
+
if (!newModel) {
|
|
755
|
+
addSystemText(C.zinc400(`Current model: ${currentModelName}`));
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
addSpacer();
|
|
759
|
+
currentModelName = newModel.replace(/^openrouter\//, '');
|
|
760
|
+
model = resolveModel(currentModelName);
|
|
761
|
+
// Update agent model
|
|
762
|
+
agent.setModel(model);
|
|
763
|
+
headerBar.update(undefined, undefined, C.zinc600(currentModelName.split('/').pop() || currentModelName));
|
|
764
|
+
addSystemText(C.emerald(`Model switched to ${currentModelName}`));
|
|
765
|
+
addSpacer();
|
|
766
|
+
tui.requestRender();
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
case '/clear': {
|
|
770
|
+
chatContainer.clear();
|
|
771
|
+
tui.requestRender();
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
case '/exit':
|
|
775
|
+
case '/quit': {
|
|
776
|
+
cleanup();
|
|
777
|
+
return true;
|
|
285
778
|
}
|
|
286
|
-
|
|
287
|
-
|
|
779
|
+
default:
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
// ── Editor submit handler ──────────────────────────────────────────────────
|
|
784
|
+
editor.onSubmit = async (input) => {
|
|
785
|
+
const trimmed = input.trim();
|
|
786
|
+
if (!trimmed)
|
|
787
|
+
return;
|
|
788
|
+
if (isProcessing)
|
|
789
|
+
return;
|
|
790
|
+
// Add to editor history
|
|
791
|
+
editor.addToHistory(trimmed);
|
|
792
|
+
// Check for slash commands
|
|
793
|
+
if (trimmed.startsWith('/')) {
|
|
794
|
+
const handled = await handleSlashCommand(trimmed);
|
|
795
|
+
if (handled)
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
// Regular message → send to agent
|
|
799
|
+
isProcessing = true;
|
|
800
|
+
// Add user message to chat
|
|
801
|
+
const userMsg = new Text(C.emerald(bold('>')) + ' ' + C.white(trimmed), 1, 0);
|
|
802
|
+
chatContainer.addChild(userMsg);
|
|
803
|
+
addSpacer();
|
|
804
|
+
tui.requestRender();
|
|
805
|
+
try {
|
|
806
|
+
await agent.prompt(trimmed);
|
|
807
|
+
}
|
|
808
|
+
catch (err) {
|
|
809
|
+
// Remove loader if present
|
|
810
|
+
if (currentLoader) {
|
|
811
|
+
currentLoader.stop();
|
|
812
|
+
chatContainer.removeChild(currentLoader);
|
|
813
|
+
currentLoader = null;
|
|
814
|
+
}
|
|
815
|
+
addSystemText(C.red(`Error: ${err.message}`));
|
|
816
|
+
addSpacer();
|
|
817
|
+
isProcessing = false;
|
|
818
|
+
}
|
|
288
819
|
};
|
|
289
|
-
|
|
820
|
+
// ── Ctrl+C handler ─────────────────────────────────────────────────────────
|
|
821
|
+
function cleanup() {
|
|
822
|
+
if (currentLoader)
|
|
823
|
+
currentLoader.stop();
|
|
824
|
+
tui.stop();
|
|
825
|
+
process.exit(0);
|
|
826
|
+
}
|
|
827
|
+
// Listen for Ctrl+C at the TUI level
|
|
828
|
+
tui.addInputListener((data) => {
|
|
829
|
+
// Ctrl+C = \x03
|
|
830
|
+
if (data === '\x03') {
|
|
831
|
+
cleanup();
|
|
832
|
+
return { consume: true };
|
|
833
|
+
}
|
|
834
|
+
return undefined;
|
|
835
|
+
});
|
|
836
|
+
// Also handle SIGINT
|
|
837
|
+
process.on('SIGINT', cleanup);
|
|
838
|
+
process.on('SIGTERM', cleanup);
|
|
839
|
+
// ── Show initial welcome ───────────────────────────────────────────────────
|
|
840
|
+
const thesisText = latestContext.thesis || latestContext.rawThesis || 'N/A';
|
|
841
|
+
const truncatedThesis = thesisText.length > 120 ? thesisText.slice(0, 120) + '...' : thesisText;
|
|
842
|
+
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
843
|
+
C.zinc200(bold(truncatedThesis)) + '\n' +
|
|
844
|
+
C.zinc600(`${latestContext.status || 'active'} ${confidencePct > 0 ? confidencePct + '%' : ''} ${(latestContext.edges || []).length} edges`) + '\n' +
|
|
845
|
+
C.zinc600('\u2500'.repeat(50)));
|
|
846
|
+
addSpacer();
|
|
847
|
+
// ── Start TUI ──────────────────────────────────────────────────────────────
|
|
848
|
+
tui.start();
|
|
290
849
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfunctions/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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
|
},
|