@sofer_agent/cli 0.3.13 → 0.3.15
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/chat.js +11 -12
- package/dist/chat.js.map +1 -1
- package/dist/tui.d.ts +37 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +316 -190
- package/dist/tui.js.map +1 -1
- package/package.json +7 -15
- package/src/chat.ts +12 -12
- package/src/tui.ts +341 -0
- package/dist/tui.jsx +0 -170
- package/dist/tui.jsx.map +0 -1
- package/src/tui.tsx +0 -227
package/dist/chat.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { agentPaths, loadConfig, shortAgentId, SoferAgent, } from "@sofer_agent/core";
|
|
4
4
|
import { loadSecrets } from "./env.js";
|
|
5
|
-
import {
|
|
5
|
+
import { SoferTui } from "./tui.js";
|
|
6
6
|
function sessionPath(config) {
|
|
7
7
|
if (config.agentObjectId)
|
|
8
8
|
return agentPaths.agent(shortAgentId(config.agentObjectId)).activityLog;
|
|
@@ -45,27 +45,26 @@ export async function chatCommand() {
|
|
|
45
45
|
if (history.length > 0)
|
|
46
46
|
agent.loadHistory(history);
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
agent.getBalance().then((bal) => {
|
|
50
|
-
|
|
51
|
-
}).catch(() => { });
|
|
52
|
-
await runTui(agent, config, async (line, signal) => {
|
|
48
|
+
const tui = new SoferTui(agent, config);
|
|
49
|
+
agent.getBalance().then((bal) => tui.setInfo({ balance: (Number(bal) / 1e9).toFixed(4) })).catch(() => { });
|
|
50
|
+
await tui.run(async (line, signal) => {
|
|
53
51
|
try {
|
|
54
52
|
const { text, turnUsage } = await agent.chat(line, signal);
|
|
55
|
-
addMessage("agent", text);
|
|
56
|
-
setInfo({ lastTurnIn: turnUsage.inputTokens, lastTurnOut: turnUsage.outputTokens });
|
|
53
|
+
tui.addMessage("agent", text);
|
|
54
|
+
tui.setInfo({ lastTurnIn: turnUsage.inputTokens, lastTurnOut: turnUsage.outputTokens });
|
|
57
55
|
}
|
|
58
56
|
catch (e) {
|
|
59
|
-
if (signal
|
|
60
|
-
addMessage("system", "turn interrupted.");
|
|
57
|
+
if (signal?.aborted) {
|
|
58
|
+
tui.addMessage("system", "turn interrupted (esc).");
|
|
61
59
|
}
|
|
62
60
|
else {
|
|
63
|
-
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
61
|
+
tui.addMessage("error", e instanceof Error ? e.message : String(e));
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
|
-
agent.getBalance().then((bal) => setInfo({ balance: (Number(bal) / 1e9).toFixed(4) })).catch(() => { });
|
|
64
|
+
agent.getBalance().then((bal) => tui.setInfo({ balance: (Number(bal) / 1e9).toFixed(4) })).catch(() => { });
|
|
67
65
|
if (sp)
|
|
68
66
|
saveSession(sp, agent.getHistory());
|
|
67
|
+
tui.render();
|
|
69
68
|
});
|
|
70
69
|
}
|
|
71
70
|
//# sourceMappingURL=chat.js.map
|
package/dist/chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,UAAU,EAEV,UAAU,EACV,YAAY,EACZ,UAAU,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/tui.d.ts
CHANGED
|
@@ -8,7 +8,41 @@ export interface TuiInfo {
|
|
|
8
8
|
lastTurnOut: number;
|
|
9
9
|
balance?: string;
|
|
10
10
|
}
|
|
11
|
-
export declare
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export declare class SoferTui {
|
|
12
|
+
private readonly agent;
|
|
13
|
+
private readonly config;
|
|
14
|
+
private state;
|
|
15
|
+
private inputBuf;
|
|
16
|
+
private cursor;
|
|
17
|
+
private columns;
|
|
18
|
+
private rows;
|
|
19
|
+
private chatW;
|
|
20
|
+
private sideW;
|
|
21
|
+
private bodyH;
|
|
22
|
+
private scrollOff;
|
|
23
|
+
private status;
|
|
24
|
+
private spinnerFrame;
|
|
25
|
+
private spinnerTimer;
|
|
26
|
+
private turnStartedAt;
|
|
27
|
+
private abortCtrl;
|
|
28
|
+
private info;
|
|
29
|
+
private resolve;
|
|
30
|
+
private onLine;
|
|
31
|
+
constructor(agent: SoferAgent, config: SoferConfig);
|
|
32
|
+
private measure;
|
|
33
|
+
addMessage(role: ChatLine["role"], text: string): void;
|
|
34
|
+
setInfo(info: Partial<TuiInfo>): void;
|
|
35
|
+
private startSpinner;
|
|
36
|
+
private stopSpinner;
|
|
37
|
+
run(onLine: (line: string, signal?: AbortSignal) => Promise<void>): Promise<void>;
|
|
38
|
+
private shutdown;
|
|
39
|
+
private handleInput;
|
|
40
|
+
private submit;
|
|
41
|
+
private handleResize;
|
|
42
|
+
render(): void;
|
|
43
|
+
private buildFrame;
|
|
44
|
+
private wrapMessage;
|
|
45
|
+
private roleLabel;
|
|
46
|
+
private buildSidebar;
|
|
47
|
+
}
|
|
14
48
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgBjE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,QAAQ;IAoBjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IApBzB,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,IAAI,CAA8C;IAC1D,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAwE;gBAGnE,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW;IAGtC,OAAO,CAAC,OAAO;IAQf,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKtD,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAIrC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,WAAW;IASb,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvF,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,WAAW,CA6BjB;YAEY,MAAM;IAgBpB,OAAO,CAAC,YAAY,CAAkD;IAEtE,MAAM,IAAI,IAAI;IAKd,OAAO,CAAC,UAAU;IA8DlB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,YAAY;CA8BrB"}
|
package/dist/tui.js
CHANGED
|
@@ -1,199 +1,325 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var _tmpl$ = /*#__PURE__*/_$template(`<box flexdirection=row marginbottom=1><text flexShrink=0></text><text wrapMode=word flexGrow=1 fg=#e5e7eb>`),
|
|
7
|
-
_tmpl$2 = /*#__PURE__*/_$template(`<box flexdirection=column><scrollbox flexgrow=1 flexshrink=1 stickyscroll stickystart=bottom></scrollbox><box flexdirection=row flexshrink=0 paddingleft=3 paddingright=2 margintop=1><text fg=#67e8f9 flexGrow=1></text></box><box flexdirection=row flexshrink=0 minheight=3 maxheight=8 borderstyle=rounded bordercolor=#374151 paddingleft=1 paddingright=1 marginleft=2 marginright=2><text fg=#67e8f9 flexShrink=0>› </text><text wrapMode=word flexGrow=1 fg=#e5e7eb></text></box><box flexdirection=row flexshrink=0 paddingleft=2 paddingright=2><text fg=#9ca3af>`);
|
|
8
|
-
import { render, useKeyboard, useTerminalDimensions } from "@opentui/solid";
|
|
9
|
-
import { For, createEffect, createSignal, onCleanup } from "solid-js";
|
|
1
|
+
const SIDEBAR_W = 28;
|
|
2
|
+
const MIN_CHAT_W = 34;
|
|
3
|
+
const GUTTER = " ";
|
|
4
|
+
const LABEL_W = 6;
|
|
5
|
+
const INDENT = `${GUTTER}${" ".repeat(LABEL_W + 1)}`;
|
|
10
6
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
11
7
|
const SPINNER_MS = 80;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
8
|
+
const B = {
|
|
9
|
+
tl: "╭", tr: "╮", bl: "╰", br: "╯",
|
|
10
|
+
h: "─", v: "│",
|
|
11
|
+
lt: "├", rt: "┤", tt: "┬", bt: "┴",
|
|
12
|
+
};
|
|
13
|
+
export class SoferTui {
|
|
14
|
+
agent;
|
|
15
|
+
config;
|
|
16
|
+
state = [];
|
|
17
|
+
inputBuf = "";
|
|
18
|
+
cursor = 0;
|
|
19
|
+
columns = 90;
|
|
20
|
+
rows = 28;
|
|
21
|
+
chatW = 58;
|
|
22
|
+
sideW = 26;
|
|
23
|
+
bodyH = 22;
|
|
24
|
+
scrollOff = Number.MAX_SAFE_INTEGER;
|
|
25
|
+
status = "idle";
|
|
26
|
+
spinnerFrame = 0;
|
|
27
|
+
spinnerTimer = null;
|
|
28
|
+
turnStartedAt = 0;
|
|
29
|
+
abortCtrl = null;
|
|
30
|
+
info = { lastTurnIn: 0, lastTurnOut: 0 };
|
|
31
|
+
resolve = null;
|
|
32
|
+
onLine = null;
|
|
33
|
+
constructor(agent, config) {
|
|
34
|
+
this.agent = agent;
|
|
35
|
+
this.config = config;
|
|
36
|
+
}
|
|
37
|
+
measure() {
|
|
38
|
+
this.columns = Math.max(80, process.stdout.columns ?? 80);
|
|
39
|
+
this.rows = Math.max(24, process.stdout.rows ?? 24);
|
|
40
|
+
this.chatW = Math.max(MIN_CHAT_W, this.columns - SIDEBAR_W - 3);
|
|
41
|
+
this.sideW = this.columns - this.chatW - 3;
|
|
42
|
+
this.bodyH = this.rows - 5;
|
|
43
|
+
}
|
|
44
|
+
addMessage(role, text) {
|
|
45
|
+
this.state.push({ role, text });
|
|
46
|
+
this.scrollOff = Number.MAX_SAFE_INTEGER;
|
|
47
|
+
}
|
|
48
|
+
setInfo(info) {
|
|
49
|
+
Object.assign(this.info, info);
|
|
50
|
+
}
|
|
51
|
+
startSpinner() {
|
|
52
|
+
if (this.spinnerTimer)
|
|
53
|
+
return;
|
|
54
|
+
this.status = "thinking";
|
|
55
|
+
this.turnStartedAt = Date.now();
|
|
56
|
+
this.spinnerFrame = 0;
|
|
57
|
+
this.spinnerTimer = setInterval(() => {
|
|
58
|
+
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
59
|
+
this.render();
|
|
60
|
+
}, SPINNER_MS);
|
|
61
|
+
}
|
|
62
|
+
stopSpinner() {
|
|
63
|
+
this.status = "idle";
|
|
64
|
+
this.turnStartedAt = 0;
|
|
65
|
+
if (this.spinnerTimer) {
|
|
66
|
+
clearInterval(this.spinnerTimer);
|
|
67
|
+
this.spinnerTimer = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async run(onLine) {
|
|
71
|
+
this.onLine = onLine;
|
|
72
|
+
this.measure();
|
|
73
|
+
if (process.stdin.isTTY)
|
|
74
|
+
process.stdin.setRawMode?.(true);
|
|
75
|
+
process.stdin.on("data", this.handleInput);
|
|
76
|
+
process.stdout.on("resize", this.handleResize);
|
|
77
|
+
process.on("SIGWINCH", this.handleResize);
|
|
78
|
+
this.render();
|
|
79
|
+
return new Promise((resolve) => { this.resolve = resolve; });
|
|
80
|
+
}
|
|
81
|
+
shutdown() {
|
|
82
|
+
this.stopSpinner();
|
|
83
|
+
process.stdin.setRawMode?.(false);
|
|
84
|
+
process.stdin.removeAllListeners("data");
|
|
85
|
+
process.stdout.removeAllListeners("resize");
|
|
86
|
+
process.removeAllListeners("SIGWINCH");
|
|
87
|
+
this.render();
|
|
88
|
+
process.stdout.write("\n");
|
|
89
|
+
this.resolve?.();
|
|
90
|
+
}
|
|
91
|
+
handleInput = (data) => {
|
|
92
|
+
const str = data.toString();
|
|
93
|
+
for (const ch of str) {
|
|
94
|
+
const code = ch.charCodeAt(0);
|
|
95
|
+
if (code === 3) {
|
|
96
|
+
this.shutdown();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (code === 4 && this.inputBuf.length === 0) {
|
|
100
|
+
this.shutdown();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (code === 27) {
|
|
104
|
+
if (this.status === "thinking" && this.abortCtrl) {
|
|
105
|
+
this.abortCtrl.abort();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.inputBuf = "";
|
|
109
|
+
this.cursor = 0;
|
|
110
|
+
this.render();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (this.status === "thinking")
|
|
114
|
+
continue;
|
|
115
|
+
if (code === 13) {
|
|
116
|
+
this.submit();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (code === 127) {
|
|
120
|
+
if (this.cursor > 0) {
|
|
121
|
+
this.inputBuf = this.inputBuf.slice(0, this.cursor - 1) + this.inputBuf.slice(this.cursor);
|
|
122
|
+
this.cursor--;
|
|
123
|
+
}
|
|
124
|
+
this.render();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (ch >= " ") {
|
|
128
|
+
this.inputBuf = this.inputBuf.slice(0, this.cursor) + ch + this.inputBuf.slice(this.cursor);
|
|
129
|
+
this.cursor++;
|
|
130
|
+
this.render();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
async submit() {
|
|
135
|
+
const line = this.inputBuf.trim();
|
|
136
|
+
this.inputBuf = "";
|
|
137
|
+
this.cursor = 0;
|
|
138
|
+
if (!line) {
|
|
139
|
+
this.render();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (line === "/exit" || line === "/quit") {
|
|
143
|
+
this.shutdown();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.addMessage("you", line);
|
|
147
|
+
this.abortCtrl = new AbortController();
|
|
148
|
+
this.startSpinner();
|
|
149
|
+
this.render();
|
|
150
|
+
await this.onLine?.(line, this.abortCtrl.signal);
|
|
151
|
+
this.abortCtrl = null;
|
|
152
|
+
this.stopSpinner();
|
|
153
|
+
this.render();
|
|
154
|
+
}
|
|
155
|
+
handleResize = () => { this.measure(); this.render(); };
|
|
156
|
+
render() {
|
|
157
|
+
this.measure();
|
|
158
|
+
process.stdout.write("\x1b[?25l\x1b[H" + this.buildFrame() + "\x1b[?25h");
|
|
159
|
+
}
|
|
160
|
+
buildFrame() {
|
|
161
|
+
const { columns, chatW, sideW, bodyH } = this;
|
|
162
|
+
const innerSideW = sideW - 2;
|
|
163
|
+
const sep = `${B.lt}${B.h.repeat(chatW)}${B.bt}${B.h.repeat(sideW)}${B.rt}`;
|
|
164
|
+
const inputW = columns - 2;
|
|
165
|
+
let buf = `${B.tl}${B.h.repeat(chatW)}${B.tt}${B.h.repeat(sideW)}${B.tr}\n`;
|
|
166
|
+
const wrapped = this.state.flatMap((m) => this.wrapMessage(m, chatW));
|
|
167
|
+
const maxScroll = Math.max(0, wrapped.length - bodyH);
|
|
168
|
+
if (this.scrollOff > maxScroll)
|
|
169
|
+
this.scrollOff = maxScroll;
|
|
170
|
+
if (this.scrollOff < 0)
|
|
171
|
+
this.scrollOff = 0;
|
|
172
|
+
const visible = wrapped.slice(this.scrollOff, this.scrollOff + bodyH);
|
|
173
|
+
const sidebarLines = this.buildSidebar(innerSideW);
|
|
174
|
+
const sbTop = Math.max(0, Math.floor((bodyH - sidebarLines.length) / 2));
|
|
175
|
+
for (let r = 0; r < bodyH; r++) {
|
|
176
|
+
buf += B.v;
|
|
177
|
+
const line = visible[r];
|
|
178
|
+
if (line)
|
|
179
|
+
buf += padTrunc(line, chatW);
|
|
180
|
+
else
|
|
181
|
+
buf += " ".repeat(chatW);
|
|
182
|
+
buf += B.v;
|
|
183
|
+
const sbIdx = r - sbTop;
|
|
184
|
+
const sbLine = (sbIdx >= 0 && sbIdx < sidebarLines.length) ? sidebarLines[sbIdx] : "";
|
|
185
|
+
buf += ` ${padRight(sbLine, innerSideW)} `;
|
|
186
|
+
buf += `${B.v}\n`;
|
|
187
|
+
}
|
|
188
|
+
buf += `${sep}\n`;
|
|
189
|
+
if (this.status === "thinking") {
|
|
190
|
+
const frame = SPINNER_FRAMES[this.spinnerFrame];
|
|
191
|
+
const elapsed = this.turnStartedAt ? formatElapsed(Date.now() - this.turnStartedAt) : "";
|
|
192
|
+
const spinnerText = `${frame} thinking…${elapsed ? ` ${elapsed}` : ""} (esc to interrupt)`;
|
|
193
|
+
const padLen = Math.max(0, inputW - 1 - spinnerText.length);
|
|
194
|
+
buf += `${B.v} \x1b[36m${spinnerText}\x1b[0m${" ".repeat(padLen)}${B.v}\n`;
|
|
195
|
+
}
|
|
196
|
+
buf += `${B.tl}${B.h.repeat(inputW)}${B.tr}\n`;
|
|
197
|
+
const cursorCh = this.status === "idle" ? "\x1b[5m▋\x1b[25m" : "";
|
|
198
|
+
const inputMaxW = inputW - 3;
|
|
199
|
+
const visibleInput = this.inputBuf.length > inputMaxW
|
|
200
|
+
? this.inputBuf.slice(this.inputBuf.length - inputMaxW)
|
|
201
|
+
: this.inputBuf;
|
|
202
|
+
const inputPad = inputMaxW - visibleInput.length;
|
|
203
|
+
buf += `${B.v} \x1b[36m>\x1b[0m ${visibleInput}${cursorCh}${" ".repeat(Math.max(0, inputPad))} ${B.v}\n`;
|
|
204
|
+
buf += `${B.bl}${B.h.repeat(inputW)}${B.br}\n`;
|
|
205
|
+
const totalIn = this.agent.usage.tokens.inputTokens;
|
|
206
|
+
const totalOut = this.agent.usage.tokens.outputTokens;
|
|
207
|
+
const cost = this.agent.usage.costUsd.toFixed(4);
|
|
208
|
+
const ft = `\x1b[90m${this.agent.address.slice(0, 10)}… · in ${totalIn} out ${totalOut} $${cost} /exit\x1b[0m`;
|
|
209
|
+
buf += `${padTrunc(ft, columns)}\n`;
|
|
210
|
+
const cursorRow = bodyH + 4 + (this.status === "thinking" ? 1 : 0);
|
|
211
|
+
const cursorCol = 4 + Math.min(this.cursor, inputMaxW);
|
|
212
|
+
buf += `\x1b[${cursorRow};${cursorCol}H`;
|
|
213
|
+
return buf;
|
|
214
|
+
}
|
|
215
|
+
wrapMessage(msg, width) {
|
|
216
|
+
const label = this.roleLabel(msg.role);
|
|
217
|
+
const bodyWidth = Math.max(10, width - GUTTER.length - LABEL_W - 1);
|
|
218
|
+
const lines = wrapText(msg.text, bodyWidth);
|
|
219
|
+
return lines.map((l, i) => i === 0 ? `${GUTTER}${label} ${l}` : `${INDENT}${l}`);
|
|
220
|
+
}
|
|
221
|
+
roleLabel(role) {
|
|
222
|
+
switch (role) {
|
|
223
|
+
case "you": return "\x1b[36myou \x1b[0m";
|
|
224
|
+
case "agent": return `\x1b[32m${this.agent.name.toLowerCase().padEnd(LABEL_W)}\x1b[0m`;
|
|
225
|
+
case "system": return "\x1b[90msys \x1b[0m";
|
|
226
|
+
case "error": return "\x1b[31merr \x1b[0m";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
buildSidebar(width) {
|
|
230
|
+
const { agent, config, info } = this;
|
|
231
|
+
const addr = `${agent.address.slice(0, 6)}…${agent.address.slice(-4)}`;
|
|
232
|
+
const totalIn = agent.usage.tokens.inputTokens;
|
|
233
|
+
const totalOut = agent.usage.tokens.outputTokens;
|
|
234
|
+
const cost = agent.usage.costUsd.toFixed(6);
|
|
235
|
+
const rows = [
|
|
236
|
+
`\x1b[1;36m${agent.name}\x1b[0m`,
|
|
237
|
+
`\x1b[90m${addr}\x1b[0m`,
|
|
238
|
+
"",
|
|
239
|
+
`${B.h.repeat(width)}`,
|
|
240
|
+
`\x1b[90mnetwork\x1b[0m ${config.network}`,
|
|
241
|
+
`\x1b[90mmodel\x1b[0m ${shortModel(config.brain.model)}`,
|
|
242
|
+
];
|
|
243
|
+
if (info.balance) {
|
|
244
|
+
rows.push("", `\x1b[90mbalance\x1b[0m \x1b[33m${info.balance} SUI\x1b[0m`);
|
|
245
|
+
}
|
|
246
|
+
rows.push("", `\x1b[90mlast\x1b[0m in ${info.lastTurnIn} out ${info.lastTurnOut}`, "", `\x1b[90mtotal\x1b[0m in ${totalIn} out ${totalOut}`, ` \x1b[33m$${cost}\x1b[0m`);
|
|
247
|
+
return rows;
|
|
248
|
+
}
|
|
32
249
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
250
|
+
function wrapText(text, width) {
|
|
251
|
+
if (!text)
|
|
252
|
+
return [""];
|
|
253
|
+
const lines = [];
|
|
254
|
+
for (const para of text.split("\n")) {
|
|
255
|
+
const words = para.split(" ");
|
|
256
|
+
let cur = "";
|
|
257
|
+
for (const w of words) {
|
|
258
|
+
if (w.length > width) {
|
|
259
|
+
if (cur) {
|
|
260
|
+
lines.push(cur);
|
|
261
|
+
cur = "";
|
|
262
|
+
}
|
|
263
|
+
for (let i = 0; i < w.length; i += width)
|
|
264
|
+
lines.push(w.slice(i, i + width));
|
|
265
|
+
}
|
|
266
|
+
else if (cur && cur.length + 1 + w.length > width) {
|
|
267
|
+
lines.push(cur);
|
|
268
|
+
cur = w;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
cur = cur ? `${cur} ${w}` : w;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (cur)
|
|
275
|
+
lines.push(cur);
|
|
276
|
+
}
|
|
277
|
+
return lines.length > 0 ? lines : [""];
|
|
38
278
|
}
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
function formatElapsed(startedAt) {
|
|
43
|
-
if (!startedAt) return "";
|
|
44
|
-
const sec = Math.floor((Date.now() - startedAt) / 1000);
|
|
45
|
-
if (sec < 60) return `${sec}s`;
|
|
46
|
-
const m = Math.floor(sec / 60);
|
|
47
|
-
return `${m}m${String(sec % 60).padStart(2, "0")}s`;
|
|
279
|
+
function stripAnsi(s) {
|
|
280
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape stripping
|
|
281
|
+
return s.replace(/\x1b\[\d*;?\d*m/g, "");
|
|
48
282
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
283
|
+
function visibleLen(s) {
|
|
284
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape stripping
|
|
285
|
+
const re = /\x1b\[\d*;?\d*m/g;
|
|
286
|
+
let len = 0;
|
|
287
|
+
let last = 0;
|
|
288
|
+
let m = re.exec(s);
|
|
289
|
+
while (m !== null) {
|
|
290
|
+
len += m.index - last;
|
|
291
|
+
last = m.index + m[0].length;
|
|
292
|
+
m = re.exec(s);
|
|
293
|
+
}
|
|
294
|
+
return len + s.length - last;
|
|
55
295
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
system: "sys",
|
|
60
|
-
error: "err"
|
|
61
|
-
};
|
|
62
|
-
const COLOR = {
|
|
63
|
-
you: "#67e8f9",
|
|
64
|
-
agent: "#86efac",
|
|
65
|
-
system: "#9ca3af",
|
|
66
|
-
error: "#fca5a5"
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// ── Components ────────────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
function MessageRow(props) {
|
|
72
|
-
return (() => {
|
|
73
|
-
var _el$ = _tmpl$(),
|
|
74
|
-
_el$2 = _el$.firstChild,
|
|
75
|
-
_el$3 = _el$2.nextSibling;
|
|
76
|
-
_$insert(_el$2, () => ` ${LABEL[props.line.role].padEnd(5)} `);
|
|
77
|
-
_$insert(_el$3, () => props.line.text);
|
|
78
|
-
_$effect(() => _$setAttribute(_el$2, "fg", COLOR[props.line.role]));
|
|
79
|
-
return _el$;
|
|
80
|
-
})();
|
|
296
|
+
function padRight(s, w) {
|
|
297
|
+
const vl = visibleLen(s);
|
|
298
|
+
return vl >= w ? s : s + " ".repeat(w - vl);
|
|
81
299
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (status() !== "thinking") {
|
|
88
|
-
setSpinnerFrame(0);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const id = setInterval(() => setSpinnerFrame(f => (f + 1) % SPINNER_FRAMES.length), SPINNER_MS);
|
|
92
|
-
onCleanup(() => clearInterval(id));
|
|
93
|
-
});
|
|
94
|
-
useKeyboard(evt => {
|
|
95
|
-
if (evt.ctrl && evt.name === "c") {
|
|
96
|
-
evt.preventDefault();
|
|
97
|
-
props.onExit();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (evt.name === "escape") {
|
|
101
|
-
const ab = activeAbort();
|
|
102
|
-
if (ab && !ab.signal.aborted) ab.abort();
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (status() === "thinking") return; // block input during turns
|
|
106
|
-
|
|
107
|
-
if (evt.name === "return") {
|
|
108
|
-
const text = input().trim();
|
|
109
|
-
if (!text) return;
|
|
110
|
-
if (text === "/exit" || text === "/quit") {
|
|
111
|
-
props.onExit();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
addMessage("you", text);
|
|
115
|
-
setInput("");
|
|
116
|
-
const ctrl = new AbortController();
|
|
117
|
-
setActiveAbort(ctrl);
|
|
118
|
-
setStatus("thinking");
|
|
119
|
-
setTurnStartedAt(Date.now());
|
|
120
|
-
props.onSubmit(text, ctrl.signal).finally(() => {
|
|
121
|
-
setStatus("idle");
|
|
122
|
-
setTurnStartedAt(null);
|
|
123
|
-
setActiveAbort(null);
|
|
124
|
-
});
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (evt.name === "backspace" || evt.name === "delete") {
|
|
128
|
-
setInput(p => p.slice(0, -1));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (evt.sequence && !evt.ctrl && !evt.meta && !evt.option) {
|
|
132
|
-
setInput(p => p + evt.sequence);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
const {
|
|
136
|
-
agent,
|
|
137
|
-
config
|
|
138
|
-
} = props;
|
|
139
|
-
const addr = () => `${agent.address.slice(0, 6)}…${agent.address.slice(-4)}`;
|
|
140
|
-
return (() => {
|
|
141
|
-
var _el$4 = _tmpl$2(),
|
|
142
|
-
_el$5 = _el$4.firstChild,
|
|
143
|
-
_el$6 = _el$5.nextSibling,
|
|
144
|
-
_el$7 = _el$6.firstChild,
|
|
145
|
-
_el$8 = _el$6.nextSibling,
|
|
146
|
-
_el$9 = _el$8.firstChild,
|
|
147
|
-
_el$0 = _el$9.nextSibling,
|
|
148
|
-
_el$1 = _el$8.nextSibling,
|
|
149
|
-
_el$10 = _el$1.firstChild;
|
|
150
|
-
_$setAttribute(_el$5, "contentoptions", {
|
|
151
|
-
flexDirection: "column",
|
|
152
|
-
paddingLeft: 0,
|
|
153
|
-
paddingRight: 1,
|
|
154
|
-
paddingTop: 1,
|
|
155
|
-
paddingBottom: 1
|
|
156
|
-
});
|
|
157
|
-
_$insert(_el$5, _$createComponent(For, {
|
|
158
|
-
get each() {
|
|
159
|
-
return rows();
|
|
160
|
-
},
|
|
161
|
-
children: line => _$createComponent(MessageRow, {
|
|
162
|
-
line: line
|
|
163
|
-
})
|
|
164
|
-
}));
|
|
165
|
-
_$insert(_el$7, () => {
|
|
166
|
-
if (status() !== "thinking") return " ";
|
|
167
|
-
spinnerFrame(); // reactive dependency
|
|
168
|
-
const elapsed = formatElapsed(turnStartedAt());
|
|
169
|
-
const frame = SPINNER_FRAMES[spinnerFrame()];
|
|
170
|
-
return elapsed ? `${frame} thinking… ${elapsed} (esc to interrupt)` : `${frame} thinking… (esc to interrupt)`;
|
|
171
|
-
});
|
|
172
|
-
_$insert(_el$0, () => `${input()}${status() === "idle" ? "▋" : ""}`);
|
|
173
|
-
_$insert(_el$10, () => [addr(), `${config.network}`, shortModel(config.brain.model), info().balance ? `${info().balance} SUI` : null, `in ${agent.usage.tokens.inputTokens} out ${agent.usage.tokens.outputTokens} $${agent.usage.costUsd.toFixed(4)}`, "/exit"].filter(Boolean).join(" · "));
|
|
174
|
-
_$effect(_p$ => {
|
|
175
|
-
var _v$ = dims().width,
|
|
176
|
-
_v$2 = dims().height;
|
|
177
|
-
_v$ !== _p$.e && _$setAttribute(_el$4, "width", _p$.e = _v$);
|
|
178
|
-
_v$2 !== _p$.t && _$setAttribute(_el$4, "height", _p$.t = _v$2);
|
|
179
|
-
return _p$;
|
|
180
|
-
}, {
|
|
181
|
-
e: undefined,
|
|
182
|
-
t: undefined
|
|
183
|
-
});
|
|
184
|
-
return _el$4;
|
|
185
|
-
})();
|
|
300
|
+
function padTrunc(s, w) {
|
|
301
|
+
const stripped = stripAnsi(s);
|
|
302
|
+
if (stripped.length <= w)
|
|
303
|
+
return s + " ".repeat(w - stripped.length);
|
|
304
|
+
return stripped.slice(0, w);
|
|
186
305
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
306
|
+
function formatElapsed(ms) {
|
|
307
|
+
const sec = Math.floor(ms / 1000);
|
|
308
|
+
if (sec < 60)
|
|
309
|
+
return `${sec}s`;
|
|
310
|
+
const m = Math.floor(sec / 60);
|
|
311
|
+
return `${m}m${String(sec % 60).padStart(2, "0")}s`;
|
|
312
|
+
}
|
|
313
|
+
function shortModel(model) {
|
|
314
|
+
const m = model.toLowerCase();
|
|
315
|
+
if (m.includes("sonnet"))
|
|
316
|
+
return "Sonnet 4";
|
|
317
|
+
if (m.includes("haiku"))
|
|
318
|
+
return "Haiku";
|
|
319
|
+
if (m.includes("opus"))
|
|
320
|
+
return "Opus 4";
|
|
321
|
+
if (m.includes("fable"))
|
|
322
|
+
return "Fable 5";
|
|
323
|
+
return model.length > 14 ? `${model.slice(0, 13)}…` : model;
|
|
199
324
|
}
|
|
325
|
+
//# sourceMappingURL=tui.js.map
|