pi-open-tui 0.2.6 → 0.2.8
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/README.md
CHANGED
|
@@ -68,12 +68,12 @@ Run `/open-tui` to open the interactive settings UI. Configuration is stored at
|
|
|
68
68
|
After each complete agent run, open-tui shows one transient notification. Tool-call turns are aggregated into that single result:
|
|
69
69
|
|
|
70
70
|
```text
|
|
71
|
-
> TPS 42.5 tok/s | ~ TTFT 1.2s | + 29.7s |
|
|
71
|
+
> TPS 42.5 tok/s | ~ TTFT 1.2s | + 29.7s | ↑ 567 | ↓ 1.2k | ! stall 1x / 4.3s | $ $3.60/M
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
The notification uses the footer's icon mode and semantic theme colors. Configure its master switch and individual TPS, TTFT, duration, token, stall, and cost segments from the **Telemetry** tab in `/open-tui`.
|
|
75
75
|
|
|
76
|
-
TPS excludes time-to-first-token
|
|
76
|
+
TPS is the actual output delivery rate: provider-reported output tokens divided by the time from the first meaningful output delta through `message_end`. It excludes time-to-first-token and tool execution between assistant messages, but includes stalls during streaming. Multi-message and multi-turn runs sum only measurable token-duration pairs before calculating the rate, so an unmeasurable short tool response does not pollute or hide a measurable final response. Messages with fewer than two meaningful deltas or less than 200 ms of observed delivery time are shown as `TPS —` unless another measurable message exists in the run. The `stall` segment shows occurrence count followed by accumulated duration. The optional `$ / M` value uses the model's list-price `usage.cost.total`; it is not the session's cumulative cost shown in the footer.
|
|
77
77
|
|
|
78
78
|
## Local development
|
|
79
79
|
|
|
@@ -95,3 +95,5 @@ This project builds on the work of several Pi community packages:
|
|
|
95
95
|
- **[pi-tps](https://github.com/monotykamary/pi-tps)** — the turn timing, stall detection, and conservative TPS measurement approach
|
|
96
96
|
|
|
97
97
|
The animated logo frames are derived from `pi-claude-code-tui`, which in turn derive from Pi's official install script (`pi.dev/install.sh`). The runtime detection list and git porcelain parsing borrow structure from `pi-zentui`.
|
|
98
|
+
|
|
99
|
+
Special thanks to the **[LINUX DO](https://linux.do)** community for their support.
|
|
Binary file
|
|
@@ -36,8 +36,9 @@ const NERD_GLYPHS: IconGlyphs = {
|
|
|
36
36
|
context: "",
|
|
37
37
|
model: "",
|
|
38
38
|
thinking: "",
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// client network view: input = upload to API, output = download from API
|
|
40
|
+
input: "",
|
|
41
|
+
output: "",
|
|
41
42
|
cacheHit: "",
|
|
42
43
|
cost: "",
|
|
43
44
|
speed: "",
|
|
@@ -67,8 +68,8 @@ const ASCII_GLYPHS: IconGlyphs = {
|
|
|
67
68
|
context: "%",
|
|
68
69
|
model: "M",
|
|
69
70
|
thinking: "~",
|
|
70
|
-
input: "
|
|
71
|
-
output: "
|
|
71
|
+
input: "↑",
|
|
72
|
+
output: "↓",
|
|
72
73
|
cacheHit: "c",
|
|
73
74
|
cost: "$",
|
|
74
75
|
speed: ">",
|
|
@@ -144,16 +144,22 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
144
144
|
private selectList: SelectList;
|
|
145
145
|
private readonly selectedItemByTab: Partial<Record<Tab, string>> = {};
|
|
146
146
|
private readonly container: Box;
|
|
147
|
+
private readonly theme: Theme;
|
|
148
|
+
private readonly onChange: (config: OpenTuiConfig) => void;
|
|
149
|
+
private readonly onClose: () => void;
|
|
147
150
|
private cachedWidth: number | undefined;
|
|
148
151
|
private cachedLines: string[] | undefined;
|
|
149
152
|
|
|
150
153
|
constructor(
|
|
151
|
-
|
|
154
|
+
theme: Theme,
|
|
152
155
|
config: OpenTuiConfig,
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
onChange: (config: OpenTuiConfig) => void,
|
|
157
|
+
onClose: () => void,
|
|
155
158
|
) {
|
|
159
|
+
this.theme = theme;
|
|
156
160
|
this.config = config;
|
|
161
|
+
this.onChange = onChange;
|
|
162
|
+
this.onClose = onClose;
|
|
157
163
|
this.container = new Box(1, 1, (s: string) => theme.bg("customMessageBg", s));
|
|
158
164
|
this.selectList = new SelectList([], 12, {
|
|
159
165
|
selectedPrefix: (t) => theme.fg("accent", t),
|
|
@@ -13,13 +13,9 @@ import type { IconMode, TelemetryConfig } from "./config.ts";
|
|
|
13
13
|
import { resolveGlyphs } from "./icons.ts";
|
|
14
14
|
import { fmtTokens, formatDuration } from "./utils.ts";
|
|
15
15
|
|
|
16
|
-
const STALL_THRESHOLD_MS =
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const MIN_INTER_CHUNK_MS = 1;
|
|
20
|
-
const MIN_GENERATION_MS = 200;
|
|
21
|
-
const STALL_DOMINANCE_RATIO = 0.85;
|
|
22
|
-
const MAX_PLAUSIBLE_TPS = 10_000;
|
|
16
|
+
const STALL_THRESHOLD_MS = 1000;
|
|
17
|
+
const MIN_STREAM_UPDATES = 2;
|
|
18
|
+
const MIN_MEASUREMENT_MS = 200;
|
|
23
19
|
|
|
24
20
|
type TelemetryEvent =
|
|
25
21
|
| AgentStartEvent
|
|
@@ -36,26 +32,25 @@ type AssistantMessage = Extract<AgentMessage, { role: "assistant" }>;
|
|
|
36
32
|
interface MessageTiming {
|
|
37
33
|
startMs: number;
|
|
38
34
|
lastUpdateMs: number;
|
|
39
|
-
|
|
35
|
+
firstOutputMs: number | null;
|
|
40
36
|
streamUpdateCount: number;
|
|
41
|
-
firstStreamUpdateMs: number | null;
|
|
42
|
-
lastStreamUpdateMs: number | null;
|
|
43
37
|
inStall: boolean;
|
|
44
38
|
}
|
|
45
39
|
|
|
40
|
+
interface MessageMeasurement {
|
|
41
|
+
outputTokens: number;
|
|
42
|
+
durationMs: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
46
45
|
interface TurnTiming {
|
|
47
46
|
startMs: number;
|
|
48
47
|
firstTokenMs: number | null;
|
|
49
48
|
currentMessage: MessageTiming | null;
|
|
50
49
|
messages: AssistantMessage[];
|
|
50
|
+
measurements: MessageMeasurement[];
|
|
51
51
|
generationMs: number;
|
|
52
|
-
streamMs: number;
|
|
53
|
-
streamIntervals: number;
|
|
54
|
-
streamUpdateCount: number;
|
|
55
52
|
stallMs: number;
|
|
56
53
|
stallCount: number;
|
|
57
|
-
isToolCall: boolean;
|
|
58
|
-
modelKey: string | null;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
export interface TurnTelemetry {
|
|
@@ -73,6 +68,11 @@ export interface TurnTelemetry {
|
|
|
73
68
|
measurementMs: number | null;
|
|
74
69
|
}
|
|
75
70
|
|
|
71
|
+
interface MeasuredTurn {
|
|
72
|
+
telemetry: TurnTelemetry;
|
|
73
|
+
measuredOutputTokens: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
76
|
function isAssistantMessage(message: AgentMessage): message is AssistantMessage {
|
|
77
77
|
return message.role === "assistant";
|
|
78
78
|
}
|
|
@@ -84,10 +84,9 @@ function round(value: number, decimals: number): number {
|
|
|
84
84
|
|
|
85
85
|
export class TurnTelemetryTracker {
|
|
86
86
|
private readonly now: () => number;
|
|
87
|
-
private readonly tpsCaps = new Map<string, number>();
|
|
88
87
|
private turn: TurnTiming | undefined;
|
|
89
88
|
private agentStartMs: number | null = null;
|
|
90
|
-
private agentTurns:
|
|
89
|
+
private agentTurns: MeasuredTurn[] = [];
|
|
91
90
|
|
|
92
91
|
constructor(now: () => number = () => performance.now()) {
|
|
93
92
|
this.now = now;
|
|
@@ -116,7 +115,6 @@ export class TurnTelemetryTracker {
|
|
|
116
115
|
this.endMessage(event.message);
|
|
117
116
|
return;
|
|
118
117
|
case "tool_execution_start":
|
|
119
|
-
if (this.turn) this.turn.isToolCall = true;
|
|
120
118
|
return;
|
|
121
119
|
case "turn_end":
|
|
122
120
|
return this.endTurnAndCollect();
|
|
@@ -129,14 +127,10 @@ export class TurnTelemetryTracker {
|
|
|
129
127
|
firstTokenMs: null,
|
|
130
128
|
currentMessage: null,
|
|
131
129
|
messages: [],
|
|
130
|
+
measurements: [],
|
|
132
131
|
generationMs: 0,
|
|
133
|
-
streamMs: 0,
|
|
134
|
-
streamIntervals: 0,
|
|
135
|
-
streamUpdateCount: 0,
|
|
136
132
|
stallMs: 0,
|
|
137
133
|
stallCount: 0,
|
|
138
|
-
isToolCall: false,
|
|
139
|
-
modelKey: null,
|
|
140
134
|
};
|
|
141
135
|
}
|
|
142
136
|
|
|
@@ -146,13 +140,10 @@ export class TurnTelemetryTracker {
|
|
|
146
140
|
this.turn.currentMessage = {
|
|
147
141
|
startMs: now,
|
|
148
142
|
lastUpdateMs: now,
|
|
149
|
-
|
|
143
|
+
firstOutputMs: null,
|
|
150
144
|
streamUpdateCount: 0,
|
|
151
|
-
firstStreamUpdateMs: null,
|
|
152
|
-
lastStreamUpdateMs: null,
|
|
153
145
|
inStall: false,
|
|
154
146
|
};
|
|
155
|
-
this.turn.modelKey ??= `${message.provider}:${message.model}`;
|
|
156
147
|
}
|
|
157
148
|
|
|
158
149
|
private updateMessage(event: MessageUpdateEvent): void {
|
|
@@ -169,16 +160,15 @@ export class TurnTelemetryTracker {
|
|
|
169
160
|
if (!turn || !current || !isAssistantMessage(message)) return;
|
|
170
161
|
|
|
171
162
|
const now = this.now();
|
|
172
|
-
if (
|
|
173
|
-
current.
|
|
163
|
+
if (current.firstOutputMs === null) {
|
|
164
|
+
current.firstOutputMs = now;
|
|
174
165
|
turn.firstTokenMs ??= now;
|
|
166
|
+
current.streamUpdateCount = 1;
|
|
175
167
|
current.lastUpdateMs = now;
|
|
176
168
|
return;
|
|
177
169
|
}
|
|
178
170
|
|
|
179
171
|
current.streamUpdateCount++;
|
|
180
|
-
current.firstStreamUpdateMs ??= now;
|
|
181
|
-
current.lastStreamUpdateMs = now;
|
|
182
172
|
|
|
183
173
|
const gap = now - current.lastUpdateMs;
|
|
184
174
|
if (gap >= STALL_THRESHOLD_MS) {
|
|
@@ -197,26 +187,31 @@ export class TurnTelemetryTracker {
|
|
|
197
187
|
|
|
198
188
|
const current = turn.currentMessage;
|
|
199
189
|
if (current) {
|
|
200
|
-
|
|
201
|
-
turn.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
190
|
+
const endMs = this.now();
|
|
191
|
+
turn.generationMs += endMs - current.startMs;
|
|
192
|
+
if (current.firstOutputMs === null && message.usage.output > 0) {
|
|
193
|
+
turn.firstTokenMs ??= endMs;
|
|
194
|
+
}
|
|
195
|
+
const measurementMs = current.firstOutputMs === null ? 0 : endMs - current.firstOutputMs;
|
|
196
|
+
if (
|
|
197
|
+
message.usage.output > 0 &&
|
|
198
|
+
current.streamUpdateCount >= MIN_STREAM_UPDATES &&
|
|
199
|
+
measurementMs >= MIN_MEASUREMENT_MS
|
|
200
|
+
) {
|
|
201
|
+
turn.measurements.push({ outputTokens: message.usage.output, durationMs: measurementMs });
|
|
206
202
|
}
|
|
207
203
|
turn.currentMessage = null;
|
|
208
204
|
}
|
|
209
205
|
turn.messages.push(message);
|
|
210
|
-
turn.modelKey ??= `${message.provider}:${message.model}`;
|
|
211
206
|
}
|
|
212
207
|
|
|
213
208
|
private endTurnAndCollect(): TurnTelemetry | undefined {
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
return telemetry;
|
|
209
|
+
const result = this.endTurn();
|
|
210
|
+
if (result && this.agentStartMs !== null) this.agentTurns.push(result);
|
|
211
|
+
return result?.telemetry;
|
|
217
212
|
}
|
|
218
213
|
|
|
219
|
-
private endTurn():
|
|
214
|
+
private endTurn(): MeasuredTurn | undefined {
|
|
220
215
|
const turn = this.turn;
|
|
221
216
|
this.turn = undefined;
|
|
222
217
|
if (!turn || turn.firstTokenMs === null || turn.messages.length === 0) return;
|
|
@@ -235,66 +230,33 @@ export class TurnTelemetryTracker {
|
|
|
235
230
|
if (![inputTokens, outputTokens, totalTokens, costUsd].every(Number.isFinite)) {
|
|
236
231
|
throw new Error("Invalid assistant usage in turn telemetry");
|
|
237
232
|
}
|
|
238
|
-
if (outputTokens <= 0 || !turn.modelKey) return;
|
|
239
|
-
|
|
240
|
-
const streamMs = turn.streamUpdateCount > 0 ? turn.streamMs : null;
|
|
241
|
-
const averageGapMs = turn.streamIntervals > 0 ? turn.streamMs / turn.streamIntervals : 0;
|
|
242
|
-
let tps: number | null = null;
|
|
243
|
-
let measurementMs: number | null = null;
|
|
244
|
-
let primary = false;
|
|
245
|
-
if (
|
|
246
|
-
streamMs !== null &&
|
|
247
|
-
streamMs >= MIN_STREAM_MS &&
|
|
248
|
-
turn.streamUpdateCount >= MIN_STREAM_UPDATES &&
|
|
249
|
-
averageGapMs >= MIN_INTER_CHUNK_MS &&
|
|
250
|
-
turn.stallMs < streamMs &&
|
|
251
|
-
streamMs - turn.stallMs >= MIN_GENERATION_MS &&
|
|
252
|
-
turn.stallMs < streamMs - turn.stallMs
|
|
253
|
-
) {
|
|
254
|
-
measurementMs = streamMs - turn.stallMs;
|
|
255
|
-
tps = round(outputTokens / (measurementMs / 1000), 1);
|
|
256
|
-
primary = true;
|
|
257
|
-
} else if (turn.streamUpdateCount >= 2 && turn.generationMs >= MIN_GENERATION_MS) {
|
|
258
|
-
let activeMs = turn.generationMs - turn.stallMs;
|
|
259
|
-
if (activeMs < MIN_GENERATION_MS || turn.stallMs > turn.generationMs * STALL_DOMINANCE_RATIO) {
|
|
260
|
-
activeMs = turn.generationMs - turn.stallMs / 2;
|
|
261
|
-
}
|
|
262
|
-
measurementMs = Math.max(activeMs, MIN_GENERATION_MS);
|
|
263
|
-
tps = round(outputTokens / (measurementMs / 1000), 1);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (tps !== null && tps > MAX_PLAUSIBLE_TPS) {
|
|
267
|
-
tps = null;
|
|
268
|
-
measurementMs = null;
|
|
269
|
-
primary = false;
|
|
270
|
-
}
|
|
271
|
-
if (primary && tps !== null) {
|
|
272
|
-
this.tpsCaps.set(turn.modelKey, Math.max(tps, this.tpsCaps.get(turn.modelKey) ?? 0));
|
|
273
|
-
}
|
|
274
|
-
if (turn.isToolCall && tps !== null) {
|
|
275
|
-
tps = this.tpsCaps.has(turn.modelKey)
|
|
276
|
-
? Math.min(tps, this.tpsCaps.get(turn.modelKey)!)
|
|
277
|
-
: null;
|
|
278
|
-
if (tps === null) measurementMs = null;
|
|
279
|
-
}
|
|
280
233
|
|
|
234
|
+
const measuredOutputTokens = turn.measurements.reduce((sum, measurement) => sum + measurement.outputTokens, 0);
|
|
235
|
+
const measuredMs = turn.measurements.reduce((sum, measurement) => sum + measurement.durationMs, 0);
|
|
236
|
+
const measurementMs = measuredMs > 0 ? measuredMs : null;
|
|
237
|
+
const tps = measurementMs === null
|
|
238
|
+
? null
|
|
239
|
+
: round(measuredOutputTokens / (measurementMs / 1000), 1);
|
|
281
240
|
const validCost = Number.isFinite(costUsd) && costUsd > 0;
|
|
282
241
|
const validTokens = Number.isFinite(totalTokens) && totalTokens > 0;
|
|
283
242
|
return {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
243
|
+
telemetry: {
|
|
244
|
+
tps,
|
|
245
|
+
ttftMs: turn.firstTokenMs - turn.startMs,
|
|
246
|
+
totalMs: endMs - turn.startMs,
|
|
247
|
+
inputTokens,
|
|
248
|
+
outputTokens,
|
|
249
|
+
stallMs: turn.stallMs,
|
|
250
|
+
stallCount: turn.stallCount,
|
|
251
|
+
rateUsdPerMTokens: validCost && validTokens
|
|
252
|
+
? round(costUsd / (totalTokens / 1_000_000), 2)
|
|
253
|
+
: null,
|
|
254
|
+
generationMs: turn.generationMs,
|
|
255
|
+
totalTokens,
|
|
256
|
+
costUsd: validCost ? costUsd : 0,
|
|
257
|
+
measurementMs,
|
|
258
|
+
},
|
|
259
|
+
measuredOutputTokens,
|
|
298
260
|
};
|
|
299
261
|
}
|
|
300
262
|
|
|
@@ -305,33 +267,29 @@ export class TurnTelemetryTracker {
|
|
|
305
267
|
this.agentTurns = [];
|
|
306
268
|
if (startMs === null || turns.length === 0) return;
|
|
307
269
|
|
|
308
|
-
const outputTokens = turns.reduce((sum, turn) => sum + turn.outputTokens, 0);
|
|
309
|
-
const inputTokens = turns.reduce((sum, turn) => sum + turn.inputTokens, 0);
|
|
310
|
-
const totalTokens = turns.reduce((sum, turn) => sum + turn.totalTokens, 0);
|
|
311
|
-
const costUsd = turns.reduce((sum, turn) => sum + turn.costUsd, 0);
|
|
312
|
-
const stallMs = turns.reduce((sum, turn) => sum + turn.stallMs, 0);
|
|
313
|
-
const stallCount = turns.reduce((sum, turn) => sum + turn.stallCount, 0);
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const measurementMs =
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
measurementMs !== null && measurementMs >= MIN_GENERATION_MS
|
|
321
|
-
? round(measuredOutputTokens / (measurementMs / 1000), 1)
|
|
322
|
-
: null;
|
|
323
|
-
const boundedTps = tps !== null && tps <= MAX_PLAUSIBLE_TPS ? tps : null;
|
|
270
|
+
const outputTokens = turns.reduce((sum, turn) => sum + turn.telemetry.outputTokens, 0);
|
|
271
|
+
const inputTokens = turns.reduce((sum, turn) => sum + turn.telemetry.inputTokens, 0);
|
|
272
|
+
const totalTokens = turns.reduce((sum, turn) => sum + turn.telemetry.totalTokens, 0);
|
|
273
|
+
const costUsd = turns.reduce((sum, turn) => sum + turn.telemetry.costUsd, 0);
|
|
274
|
+
const stallMs = turns.reduce((sum, turn) => sum + turn.telemetry.stallMs, 0);
|
|
275
|
+
const stallCount = turns.reduce((sum, turn) => sum + turn.telemetry.stallCount, 0);
|
|
276
|
+
const measuredOutputTokens = turns.reduce((sum, turn) => sum + turn.measuredOutputTokens, 0);
|
|
277
|
+
const measuredMs = turns.reduce((sum, turn) => sum + (turn.telemetry.measurementMs ?? 0), 0);
|
|
278
|
+
const measurementMs = measuredMs > 0 ? measuredMs : null;
|
|
279
|
+
const tps = measurementMs === null
|
|
280
|
+
? null
|
|
281
|
+
: round(measuredOutputTokens / (measurementMs / 1000), 1);
|
|
324
282
|
const validRate = costUsd > 0 && totalTokens > 0;
|
|
325
283
|
return {
|
|
326
|
-
tps
|
|
327
|
-
ttftMs: turns[0]!.ttftMs,
|
|
284
|
+
tps,
|
|
285
|
+
ttftMs: turns[0]!.telemetry.ttftMs,
|
|
328
286
|
totalMs: this.now() - startMs,
|
|
329
287
|
inputTokens,
|
|
330
288
|
outputTokens,
|
|
331
289
|
stallMs,
|
|
332
290
|
stallCount,
|
|
333
291
|
rateUsdPerMTokens: validRate ? round(costUsd / (totalTokens / 1_000_000), 2) : null,
|
|
334
|
-
generationMs: turns.reduce((sum, turn) => sum + turn.generationMs, 0),
|
|
292
|
+
generationMs: turns.reduce((sum, turn) => sum + turn.telemetry.generationMs, 0),
|
|
335
293
|
totalTokens,
|
|
336
294
|
costUsd,
|
|
337
295
|
measurementMs,
|
|
@@ -362,8 +320,8 @@ export function formatTurnTelemetry(
|
|
|
362
320
|
parts.push(theme.fg("success", `${glyphs.done} ${formatTurnDuration(telemetry.totalMs)}`));
|
|
363
321
|
}
|
|
364
322
|
if (config.tokens) {
|
|
365
|
-
parts.push(theme.fg("accent", `${glyphs.input}
|
|
366
|
-
parts.push(theme.fg("success", `${glyphs.output}
|
|
323
|
+
parts.push(theme.fg("accent", `${glyphs.input} ${fmtTokens(telemetry.inputTokens)}`));
|
|
324
|
+
parts.push(theme.fg("success", `${glyphs.output} ${fmtTokens(telemetry.outputTokens)}`));
|
|
367
325
|
}
|
|
368
326
|
if (config.stalls && telemetry.stallMs > 0) {
|
|
369
327
|
parts.push(theme.fg("warning", `${glyphs.stall} stall ${telemetry.stallCount}x / ${formatTurnDuration(telemetry.stallMs)}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-open-tui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "A polished TUI for Pi coding agent: animated logo header, Starship-style footer, rounded editor with model metadata, and prompt-box user messages. Combines the best of pi-haiku, pi-claude-code-tui, and pi-zentui.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"LICENSE"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test": "node --test
|
|
27
|
+
"test": "node --test tests/settings-command.test.ts tests/footer.test.ts tests/editor.test.ts tests/telemetry.test.ts",
|
|
28
28
|
"typecheck": "tsc --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"pi": {
|