pi-bar 0.3.4 → 0.3.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/README.md +29 -11
- package/extensions/status-footer.ts +164 -11
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -3,22 +3,23 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-bar)
|
|
4
4
|
[](https://www.npmjs.com/package/pi-bar)
|
|
5
5
|
|
|
6
|
-
**Never accidentally run Opus on a typo again.** pi-bar puts the active model, thinking level, context usage, and extension statuses in pi's footer so you catch wrong-model / wrong-reasoning / context-pressure problems before they cost you.
|
|
6
|
+
**Never accidentally run Opus on a typo again.** pi-bar puts the active model, thinking level, context usage, live TLDR, and extension statuses in pi's footer so you catch wrong-model / wrong-reasoning / context-pressure problems before they cost you.
|
|
7
7
|
|
|
8
8
|
```text
|
|
9
|
-
◈ claude-opus-4.7 ❯ ✦ think:med ❯ ◷ 2.6% / 1.0M ❯ ✧ plan:active
|
|
9
|
+
◈ claude-opus-4.7 ❯ ✦ think:med ❯ ◷ 2.6% / 1.0M ❯ ⓘ Inspecting package structure ❯ ✧ plan:active
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-

|
|
13
13
|
|
|
14
14
|
## Why use it?
|
|
15
15
|
|
|
16
16
|
- **See the active model at a glance** — catch accidental model switches before an expensive or sensitive task starts.
|
|
17
17
|
- **Track thinking level in place** — immediately notice when you are using the wrong reasoning setting.
|
|
18
18
|
- **Watch context pressure early** — context usage turns green, yellow, then red as you approach the limit.
|
|
19
|
+
- **Show pi-tldr inline** — when `pi-tldr` is installed, its current TLDR appears as a compact footer segment.
|
|
19
20
|
- **Keep extension statuses visible** — statuses set with `ctx.ui.setStatus()` still appear in the custom footer.
|
|
20
21
|
|
|
21
|
-
pi-bar is intentionally tiny: one small extension, no broad behavior changes, and no commands to learn. It replaces pi's built-in footer with a compact model / thinking / context / extension-status line that refreshes when model, thinking level, context usage, or extension statuses change.
|
|
22
|
+
pi-bar is intentionally tiny: one small extension, no broad behavior changes, and no commands to learn. It replaces pi's built-in footer with a compact model / thinking / context / tldr / extension-status line that refreshes when model, thinking level, context usage, pi-tldr, or extension statuses change.
|
|
22
23
|
|
|
23
24
|
## Install
|
|
24
25
|
|
|
@@ -39,11 +40,27 @@ pi-bar works out of the box, but you can tune it with environment variables befo
|
|
|
39
40
|
### Show or hide segments
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
|
-
PI_BAR_SHOW=model,thinking,context,extensions pi
|
|
43
|
+
PI_BAR_SHOW=model,thinking,context,tldr,extensions pi
|
|
43
44
|
PI_BAR_SHOW=model,context pi
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
Allowed segments are `model`, `thinking`, `context`, and `extensions`. The `extensions` segment is hidden when no extension has set a status.
|
|
47
|
+
Allowed segments are `model`, `thinking`, `context`, `tldr`, and `extensions`. The `tldr` segment is hidden unless `pi-tldr` has a current TLDR. The `extensions` segment is hidden when no extension has set a status.
|
|
48
|
+
|
|
49
|
+
### Show pi-tldr in the footer
|
|
50
|
+
|
|
51
|
+
Install [pi-tldr](https://www.npmjs.com/package/pi-tldr) alongside pi-bar:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pi install npm:pi-tldr
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
pi-bar captures pi-tldr's `pi-tldr` widget and renders its current text as the `tldr` segment. Limit segment width with:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
PI_BAR_TLDR_MAX_WIDTH=48 pi
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Default is `64`. Set `PI_BAR_SHOW` without `tldr` to hide it.
|
|
47
64
|
|
|
48
65
|
### Configure extension statuses
|
|
49
66
|
|
|
@@ -56,7 +73,7 @@ Use `/bar` inside pi to open an interactive status-visibility picker.
|
|
|
56
73
|
|
|
57
74
|
Toggle each status key between `shown` and `hidden`. The `New statuses` row controls whether status keys discovered later are shown or hidden by default.
|
|
58
75
|
|
|
59
|
-
Status filter choices persist
|
|
76
|
+
Status filter choices persist globally across pi sessions. Set `PI_BAR_CONFIG=/path/to/config.json` before launching pi to override the default config path (`~/.pi/agent/pi-bar.json`). Advanced command forms like `/bar status hide <key>` still work for scripting.
|
|
60
77
|
|
|
61
78
|
### Change context thresholds
|
|
62
79
|
|
|
@@ -76,10 +93,10 @@ PI_BAR_ICONS=nerdfont pi
|
|
|
76
93
|
|
|
77
94
|
Supported presets:
|
|
78
95
|
|
|
79
|
-
- `unicode` — default, e.g. `◈ gpt-5.5 ❯ ✦ think:medium ❯ ◷ 42.7% / 272k ❯ ✧ plan:active`
|
|
80
|
-
- `plain` — maximum compatibility, e.g. `model:gpt-5.5 | think:medium | ctx:42.7% / 272k | ext:plan:active`
|
|
81
|
-
- `emoji` — colorful emoji markers, e.g. `🤖 gpt-5.5 › ✨ think:medium › 🧭 42.7% / 272k › 🧩 plan:active`
|
|
82
|
-
- `nerdfont` — icon-font glyphs for terminals configured with a Nerd Font, e.g. ` gpt-5.5 think:medium 42.7% / 272k plan:active`
|
|
96
|
+
- `unicode` — default, e.g. `◈ gpt-5.5 ❯ ✦ think:medium ❯ ◷ 42.7% / 272k ❯ ⓘ Reading files ❯ ✧ plan:active`
|
|
97
|
+
- `plain` — maximum compatibility, e.g. `model:gpt-5.5 | think:medium | ctx:42.7% / 272k | tldr:Reading files | ext:plan:active`
|
|
98
|
+
- `emoji` — colorful emoji markers, e.g. `🤖 gpt-5.5 › ✨ think:medium › 🧭 42.7% / 272k › 📝 Reading files › 🧩 plan:active`
|
|
99
|
+
- `nerdfont` — icon-font glyphs for terminals configured with a Nerd Font, e.g. ` gpt-5.5 think:medium 42.7% / 272k Reading files plan:active`
|
|
83
100
|
|
|
84
101
|
## Compatibility
|
|
85
102
|
|
|
@@ -91,6 +108,7 @@ PI_BAR_ICONS=plain pi
|
|
|
91
108
|
|
|
92
109
|
## Pairs well with
|
|
93
110
|
|
|
111
|
+
- **[pi-tldr](https://www.npmjs.com/package/pi-tldr)** — show live summaries above the input bar; pi-bar mirrors them into the footer.
|
|
94
112
|
- **[pi-chrome](https://www.npmjs.com/package/pi-chrome)** — give your Pi agent your real, signed-in Chrome. Use pi-bar's red-context threshold as the signal to wrap up long browser scrapes before context overflows.
|
|
95
113
|
- **[pi-qq](https://www.npmjs.com/package/pi-qq)** — ask side questions about what the agent just did without polluting the transcript.
|
|
96
114
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* pi-bar — footer/statusline extension.
|
|
3
3
|
*
|
|
4
4
|
* Replaces the built-in footer with left-aligned segments:
|
|
5
|
-
* ◈ <model name> ❯ ✦ think:<level> ❯ ◷ <context% / window> ❯ ✧ <extension statuses>
|
|
5
|
+
* ◈ <model name> ❯ ✦ think:<level> ❯ ◷ <context% / window> ❯ ⓘ <tldr> ❯ ✧ <extension statuses>
|
|
6
6
|
*
|
|
7
7
|
* Examples:
|
|
8
8
|
* ◈ claude-opus-4.7 ❯ ✦ think:med ❯ ◷ 2.6% / 1.0M
|
|
@@ -11,24 +11,32 @@
|
|
|
11
11
|
* and after each assistant turn so context usage stays current.
|
|
12
12
|
*
|
|
13
13
|
* Configuration env vars:
|
|
14
|
-
* PI_BAR_SHOW=model,thinking,context,extensions
|
|
14
|
+
* PI_BAR_SHOW=model,thinking,context,tldr,extensions
|
|
15
15
|
* PI_BAR_THRESHOLDS=70,90
|
|
16
16
|
* PI_BAR_ICONS=unicode|plain|emoji|nerdfont
|
|
17
|
+
* PI_BAR_TLDR_MAX_WIDTH=64
|
|
17
18
|
*/
|
|
18
19
|
|
|
20
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
19
23
|
import {
|
|
20
24
|
getSettingsListTheme,
|
|
21
25
|
type ExtensionAPI,
|
|
22
26
|
type ExtensionContext,
|
|
27
|
+
type Theme,
|
|
28
|
+
type ThemeColor,
|
|
23
29
|
} from "@earendil-works/pi-coding-agent";
|
|
24
30
|
import {
|
|
25
31
|
Container,
|
|
32
|
+
type Component,
|
|
26
33
|
type SettingItem,
|
|
27
34
|
SettingsList,
|
|
35
|
+
type TUI,
|
|
28
36
|
truncateToWidth,
|
|
29
37
|
} from "@earendil-works/pi-tui";
|
|
30
38
|
|
|
31
|
-
type SegmentName = "model" | "thinking" | "context" | "extensions";
|
|
39
|
+
type SegmentName = "model" | "thinking" | "context" | "tldr" | "extensions";
|
|
32
40
|
type IconPreset = "unicode" | "plain" | "emoji" | "nerdfont";
|
|
33
41
|
type StatusFilter =
|
|
34
42
|
| { mode: "all"; hidden: Set<string> }
|
|
@@ -36,13 +44,25 @@ type StatusFilter =
|
|
|
36
44
|
type SerializedStatusFilter =
|
|
37
45
|
| { mode: "all"; hidden: string[] }
|
|
38
46
|
| { mode: "only"; shown: string[] };
|
|
47
|
+
type TldrWidgetFactory = (tui: TUI, theme: Theme) => Component & {
|
|
48
|
+
dispose?(): void;
|
|
49
|
+
};
|
|
50
|
+
type CapturedTldrWidget =
|
|
51
|
+
| { kind: "lines"; lines: string[] }
|
|
52
|
+
| { kind: "factory"; factory: TldrWidgetFactory };
|
|
39
53
|
|
|
40
54
|
const STATUS_FILTER_ENTRY_TYPE = "pi-bar-status-filter";
|
|
55
|
+
const TLDR_WIDGET_KEY = "pi-tldr";
|
|
56
|
+
const DEFAULT_TLDR_MAX_WIDTH = 64;
|
|
57
|
+
const ANSI_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
58
|
+
const CONFIG_PATH =
|
|
59
|
+
process.env.PI_BAR_CONFIG ?? join(homedir(), ".pi", "agent", "pi-bar.json");
|
|
41
60
|
|
|
42
61
|
const DEFAULT_SEGMENTS: SegmentName[] = [
|
|
43
62
|
"model",
|
|
44
63
|
"thinking",
|
|
45
64
|
"context",
|
|
65
|
+
"tldr",
|
|
46
66
|
"extensions",
|
|
47
67
|
];
|
|
48
68
|
const DEFAULT_WARNING_THRESHOLD = 70;
|
|
@@ -54,6 +74,7 @@ const ICON_PRESETS: Record<
|
|
|
54
74
|
model: string;
|
|
55
75
|
thinking: string;
|
|
56
76
|
context: string;
|
|
77
|
+
tldr: string;
|
|
57
78
|
extensions: string;
|
|
58
79
|
separator: string;
|
|
59
80
|
}
|
|
@@ -62,6 +83,7 @@ const ICON_PRESETS: Record<
|
|
|
62
83
|
model: "◈",
|
|
63
84
|
thinking: "✦",
|
|
64
85
|
context: "◷",
|
|
86
|
+
tldr: "ⓘ",
|
|
65
87
|
extensions: "✧",
|
|
66
88
|
separator: "❯",
|
|
67
89
|
},
|
|
@@ -69,6 +91,7 @@ const ICON_PRESETS: Record<
|
|
|
69
91
|
model: "model",
|
|
70
92
|
thinking: "think",
|
|
71
93
|
context: "ctx",
|
|
94
|
+
tldr: "tldr",
|
|
72
95
|
extensions: "ext",
|
|
73
96
|
separator: "|",
|
|
74
97
|
},
|
|
@@ -76,6 +99,7 @@ const ICON_PRESETS: Record<
|
|
|
76
99
|
model: "🤖",
|
|
77
100
|
thinking: "✨",
|
|
78
101
|
context: "🧭",
|
|
102
|
+
tldr: "📝",
|
|
79
103
|
extensions: "🧩",
|
|
80
104
|
separator: "›",
|
|
81
105
|
},
|
|
@@ -83,6 +107,7 @@ const ICON_PRESETS: Record<
|
|
|
83
107
|
model: "",
|
|
84
108
|
thinking: "",
|
|
85
109
|
context: "",
|
|
110
|
+
tldr: "",
|
|
86
111
|
extensions: "",
|
|
87
112
|
separator: "",
|
|
88
113
|
},
|
|
@@ -106,7 +131,7 @@ function formatModelName(id: string | undefined): string {
|
|
|
106
131
|
return base.replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
107
132
|
}
|
|
108
133
|
|
|
109
|
-
function thinkingColor(level: string):
|
|
134
|
+
function thinkingColor(level: string): ThemeColor {
|
|
110
135
|
switch (level) {
|
|
111
136
|
case "off":
|
|
112
137
|
return "thinkingOff";
|
|
@@ -132,7 +157,7 @@ function contextColor(
|
|
|
132
157
|
percent: number | null | undefined,
|
|
133
158
|
warningThreshold: number,
|
|
134
159
|
errorThreshold: number,
|
|
135
|
-
):
|
|
160
|
+
): ThemeColor {
|
|
136
161
|
if (percent === null || percent === undefined) return "muted";
|
|
137
162
|
if (percent >= errorThreshold) return "error";
|
|
138
163
|
if (percent >= warningThreshold) return "warning";
|
|
@@ -147,7 +172,7 @@ function parseSegments(): SegmentName[] {
|
|
|
147
172
|
.split(",")
|
|
148
173
|
.map((segment) => segment.trim().toLowerCase())
|
|
149
174
|
.filter((segment): segment is SegmentName =>
|
|
150
|
-
["model", "thinking", "context", "extensions"].includes(segment),
|
|
175
|
+
["model", "thinking", "context", "tldr", "extensions"].includes(segment),
|
|
151
176
|
);
|
|
152
177
|
|
|
153
178
|
return requested.length > 0 ? requested : DEFAULT_SEGMENTS;
|
|
@@ -187,6 +212,63 @@ function parseIconPreset(): IconPreset {
|
|
|
187
212
|
return "unicode";
|
|
188
213
|
}
|
|
189
214
|
|
|
215
|
+
function parseTldrMaxWidth(): number {
|
|
216
|
+
const raw = process.env.PI_BAR_TLDR_MAX_WIDTH;
|
|
217
|
+
if (!raw) return DEFAULT_TLDR_MAX_WIDTH;
|
|
218
|
+
|
|
219
|
+
const value = Number.parseInt(raw, 10);
|
|
220
|
+
if (Number.isFinite(value) && value >= 10 && value <= 500) return value;
|
|
221
|
+
return DEFAULT_TLDR_MAX_WIDTH;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function stripAnsi(value: string): string {
|
|
225
|
+
return value.replace(ANSI_PATTERN, "");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function extractTldrText(lines: readonly string[]): string | null {
|
|
229
|
+
const parts = lines
|
|
230
|
+
.map(stripAnsi)
|
|
231
|
+
.map((line) => line.trimEnd())
|
|
232
|
+
.filter((line) => line.trim().length > 0)
|
|
233
|
+
.filter((line) => !/^[╭╰].*[╮╯]$/.test(line.trim()))
|
|
234
|
+
.map((line) => {
|
|
235
|
+
const trimmed = line.trim();
|
|
236
|
+
if (trimmed.startsWith("│") && trimmed.endsWith("│")) {
|
|
237
|
+
return trimmed.slice(1, -1).trim();
|
|
238
|
+
}
|
|
239
|
+
return trimmed.replace(/^tldr:\s*/i, "").trim();
|
|
240
|
+
})
|
|
241
|
+
.filter(Boolean);
|
|
242
|
+
|
|
243
|
+
const text = parts.join(" ").replace(/\s+/g, " ").trim();
|
|
244
|
+
return text.length > 0 ? text : null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function renderCapturedTldr(
|
|
248
|
+
widget: CapturedTldrWidget | undefined,
|
|
249
|
+
tui: TUI,
|
|
250
|
+
theme: Theme,
|
|
251
|
+
width: number,
|
|
252
|
+
maxWidth: number,
|
|
253
|
+
): string | null {
|
|
254
|
+
if (!widget) return null;
|
|
255
|
+
|
|
256
|
+
let lines: string[];
|
|
257
|
+
if (widget.kind === "lines") {
|
|
258
|
+
lines = widget.lines;
|
|
259
|
+
} else {
|
|
260
|
+
const component = widget.factory(tui, theme);
|
|
261
|
+
try {
|
|
262
|
+
lines = component.render(Math.max(12, width));
|
|
263
|
+
} finally {
|
|
264
|
+
component.dispose?.();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const text = extractTldrText(lines);
|
|
269
|
+
return text ? truncateToWidth(text, maxWidth) : null;
|
|
270
|
+
}
|
|
271
|
+
|
|
190
272
|
function prefixed(icon: string, text: string, preset: IconPreset): string {
|
|
191
273
|
if (preset === "plain") return `${icon}:${text}`;
|
|
192
274
|
return `${icon} ${text}`;
|
|
@@ -250,6 +332,29 @@ function describeStatusFilter(filter: StatusFilter): string {
|
|
|
250
332
|
return hidden.length > 0 ? `showing all except: ${hidden.join(", ")}` : "showing all";
|
|
251
333
|
}
|
|
252
334
|
|
|
335
|
+
function readGlobalStatusFilter(): StatusFilter | null {
|
|
336
|
+
try {
|
|
337
|
+
const data = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as {
|
|
338
|
+
statusFilter?: unknown;
|
|
339
|
+
};
|
|
340
|
+
return parseSerializedStatusFilter(data.statusFilter ?? data);
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function writeGlobalStatusFilter(filter: StatusFilter): void {
|
|
347
|
+
const data = JSON.stringify(
|
|
348
|
+
{ statusFilter: serializeStatusFilter(filter) },
|
|
349
|
+
null,
|
|
350
|
+
2,
|
|
351
|
+
);
|
|
352
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
353
|
+
const tmpPath = `${CONFIG_PATH}.${process.pid}.tmp`;
|
|
354
|
+
writeFileSync(tmpPath, `${data}\n`, "utf8");
|
|
355
|
+
renameSync(tmpPath, CONFIG_PATH);
|
|
356
|
+
}
|
|
357
|
+
|
|
253
358
|
function getKnownStatusKeys(filter: StatusFilter, seenStatusKeys: Set<string>): string[] {
|
|
254
359
|
const keys = new Set(seenStatusKeys);
|
|
255
360
|
if (filter.mode === "only") {
|
|
@@ -263,19 +368,52 @@ function getKnownStatusKeys(filter: StatusFilter, seenStatusKeys: Set<string>):
|
|
|
263
368
|
export default function (pi: ExtensionAPI) {
|
|
264
369
|
let requestRender: (() => void) | undefined;
|
|
265
370
|
let statusFilter: StatusFilter = { mode: "all", hidden: new Set() };
|
|
371
|
+
let capturedTldrWidget: CapturedTldrWidget | undefined;
|
|
266
372
|
const seenStatusKeys = new Set<string>();
|
|
373
|
+
const patchedUis = new WeakSet<object>();
|
|
267
374
|
const refresh = () => requestRender?.();
|
|
375
|
+
const installTldrWidgetCapture = (ctx: ExtensionContext) => {
|
|
376
|
+
if (!ctx.hasUI) return;
|
|
377
|
+
const ui = ctx.ui as ExtensionContext["ui"];
|
|
378
|
+
if (patchedUis.has(ui)) return;
|
|
379
|
+
patchedUis.add(ui);
|
|
380
|
+
|
|
381
|
+
const originalSetWidget = ui.setWidget.bind(ui);
|
|
382
|
+
ui.setWidget = ((key: string, content: unknown, options?: unknown) => {
|
|
383
|
+
if (key === TLDR_WIDGET_KEY) {
|
|
384
|
+
if (Array.isArray(content)) {
|
|
385
|
+
capturedTldrWidget = { kind: "lines", lines: content };
|
|
386
|
+
} else if (typeof content === "function") {
|
|
387
|
+
capturedTldrWidget = {
|
|
388
|
+
kind: "factory",
|
|
389
|
+
factory: content as TldrWidgetFactory,
|
|
390
|
+
};
|
|
391
|
+
} else {
|
|
392
|
+
capturedTldrWidget = undefined;
|
|
393
|
+
}
|
|
394
|
+
refresh();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return originalSetWidget(
|
|
398
|
+
key,
|
|
399
|
+
content as Parameters<ExtensionContext["ui"]["setWidget"]>[1],
|
|
400
|
+
options as Parameters<ExtensionContext["ui"]["setWidget"]>[2],
|
|
401
|
+
);
|
|
402
|
+
}) as ExtensionContext["ui"]["setWidget"];
|
|
403
|
+
};
|
|
268
404
|
const restoreStatusFilter = (ctx: ExtensionContext) => {
|
|
269
|
-
let restoredFilter
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
405
|
+
let restoredFilter = readGlobalStatusFilter();
|
|
406
|
+
if (!restoredFilter) {
|
|
407
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
408
|
+
if (entry.type === "custom" && entry.customType === STATUS_FILTER_ENTRY_TYPE) {
|
|
409
|
+
restoredFilter = parseSerializedStatusFilter(entry.data);
|
|
410
|
+
}
|
|
273
411
|
}
|
|
274
412
|
}
|
|
275
413
|
statusFilter = restoredFilter ?? { mode: "all", hidden: new Set() };
|
|
276
414
|
};
|
|
277
415
|
const persistStatusFilter = () => {
|
|
278
|
-
|
|
416
|
+
writeGlobalStatusFilter(statusFilter);
|
|
279
417
|
refresh();
|
|
280
418
|
};
|
|
281
419
|
const openStatusConfigurator = async (ctx: ExtensionContext) => {
|
|
@@ -451,11 +589,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
451
589
|
pi.on("turn_end", async () => refresh());
|
|
452
590
|
pi.on("message_end", async () => refresh());
|
|
453
591
|
pi.on("session_tree", async (_event, ctx) => {
|
|
592
|
+
installTldrWidgetCapture(ctx);
|
|
454
593
|
restoreStatusFilter(ctx);
|
|
455
594
|
refresh();
|
|
456
595
|
});
|
|
457
596
|
|
|
458
597
|
pi.on("session_start", async (_event, ctx) => {
|
|
598
|
+
capturedTldrWidget = undefined;
|
|
599
|
+
installTldrWidgetCapture(ctx);
|
|
459
600
|
restoreStatusFilter(ctx);
|
|
460
601
|
|
|
461
602
|
if (!ctx.hasUI) return;
|
|
@@ -464,6 +605,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
464
605
|
const iconPreset = parseIconPreset();
|
|
465
606
|
const icons = ICON_PRESETS[iconPreset];
|
|
466
607
|
const { warningThreshold, errorThreshold } = parseThresholds();
|
|
608
|
+
const tldrMaxWidth = parseTldrMaxWidth();
|
|
467
609
|
|
|
468
610
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
469
611
|
requestRender = () => tui.requestRender();
|
|
@@ -491,6 +633,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
491
633
|
const contextText = usage
|
|
492
634
|
? `${usage.percent !== null ? `${usage.percent.toFixed(1)}%` : "—%"} / ${formatTokens(usage.contextWindow)}`
|
|
493
635
|
: "—";
|
|
636
|
+
const tldrText = renderCapturedTldr(
|
|
637
|
+
capturedTldrWidget,
|
|
638
|
+
tui,
|
|
639
|
+
theme,
|
|
640
|
+
width,
|
|
641
|
+
tldrMaxWidth,
|
|
642
|
+
);
|
|
494
643
|
|
|
495
644
|
const segmentRenderers: Record<SegmentName, string | null> = {
|
|
496
645
|
model: theme.fg(
|
|
@@ -505,6 +654,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
505
654
|
contextSegmentColor,
|
|
506
655
|
prefixed(icons.context, contextText, iconPreset),
|
|
507
656
|
),
|
|
657
|
+
tldr: tldrText
|
|
658
|
+
? theme.fg("muted", prefixed(icons.tldr, tldrText, iconPreset))
|
|
659
|
+
: null,
|
|
508
660
|
extensions: extensionStatuses
|
|
509
661
|
? theme.fg(
|
|
510
662
|
"muted",
|
|
@@ -526,6 +678,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
526
678
|
});
|
|
527
679
|
|
|
528
680
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
681
|
+
capturedTldrWidget = undefined;
|
|
529
682
|
if (ctx.hasUI) ctx.ui.setFooter(undefined);
|
|
530
683
|
});
|
|
531
684
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bar",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Never accidentally run Opus on a typo. Pi footer/statusline showing active model, thinking level, context usage, and extension statuses.",
|
|
3
|
+
"version": "0.3.6",
|
|
4
|
+
"description": "Never accidentally run Opus on a typo. Pi footer/statusline showing active model, thinking level, context usage, pi-tldr, and extension statuses.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"model",
|
|
14
14
|
"thinking",
|
|
15
15
|
"context",
|
|
16
|
-
"context-window"
|
|
16
|
+
"context-window",
|
|
17
|
+
"tldr",
|
|
18
|
+
"pi-tldr"
|
|
17
19
|
],
|
|
18
20
|
"license": "MIT",
|
|
19
21
|
"repository": {
|
|
@@ -46,6 +48,6 @@
|
|
|
46
48
|
"extensions": [
|
|
47
49
|
"./extensions/status-footer.ts"
|
|
48
50
|
],
|
|
49
|
-
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.
|
|
51
|
+
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.6/assets/screenshot.png"
|
|
50
52
|
}
|
|
51
53
|
}
|