pi-bar 0.3.40 → 0.3.43
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 +53 -4
- package/extensions/status-footer.ts +190 -33
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
**Never accidentally run Opus on a typo again.** pi-bar keeps your model, thinking level, context pressure, a live progress update, and any extension statuses visible in pi's footer.
|
|
7
7
|
|
|
8
8
|
```text
|
|
9
|
-
claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Reviewing package structure ❯
|
|
9
|
+
claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Reviewing package structure ❯ Plan active ❯ Queue: 2
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|

|
|
@@ -35,6 +35,19 @@ If pi is already running after install, reload resources:
|
|
|
35
35
|
/reload
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Automatic layout
|
|
39
|
+
|
|
40
|
+
The footer adapts to terminal width without extra configuration:
|
|
41
|
+
|
|
42
|
+
- **Model, thinking level, and context percentage take priority.**
|
|
43
|
+
- Separators have two spaces on each side by default, for a roomier display.
|
|
44
|
+
- Progress text shrinks first. If space is still tight, separator padding reduces to one space per side before compacting content. CWD then compacts from its preferred path to `parent/project`, then `project`; context window size is omitted when needed.
|
|
45
|
+
- Extension badges stay in their published order. Badges that cannot fit are hidden whole, with `+N` showing how many are behind the overflow. Open `/bar status` to inspect their text and visibility by key.
|
|
46
|
+
- On very narrow terminals, progress and CWD yield to core information. If even the core cannot fit, the model name is truncated first. At extreme widths, not every value or overflow count can remain visible.
|
|
47
|
+
- Widening the terminal restores the full display. Thinking and context retain their semantic colors throughout.
|
|
48
|
+
|
|
49
|
+
Existing segment visibility settings still apply; CWD remains opt-in.
|
|
50
|
+
|
|
38
51
|
## Customization
|
|
39
52
|
|
|
40
53
|
pi-bar works out of the box. Run `/bar` inside pi to choose which footer segments and extension statuses are shown:
|
|
@@ -43,7 +56,7 @@ pi-bar works out of the box. Run `/bar` inside pi to choose which footer segment
|
|
|
43
56
|
/bar
|
|
44
57
|
```
|
|
45
58
|
|
|
46
|
-
Toggle `Model`, `Thinking level`, `Context usage`, `Progress update`, and `Extension statuses` between `shown` and `hidden`. If other extensions have published status badges, `/bar` also shows fine-grained `Status: <key>` rows plus a `New extension statuses` default. You can also use commands:
|
|
59
|
+
Toggle `Model`, `Thinking level`, `Context usage`, `Current directory`, `Progress update`, and `Extension statuses` between `shown` and `hidden`. If other extensions have published status badges, `/bar` also shows fine-grained `Status: <key>` rows plus a `New extension statuses` default. You can also use commands:
|
|
47
60
|
|
|
48
61
|
```text
|
|
49
62
|
/bar segments list
|
|
@@ -52,7 +65,7 @@ Toggle `Model`, `Thinking level`, `Context usage`, `Progress update`, and `Exten
|
|
|
52
65
|
/bar segments show thinking
|
|
53
66
|
```
|
|
54
67
|
|
|
55
|
-
Allowed segments are `model`, `thinking`, `context`, `progress`, and `extensions`. The `progress` segment stays hidden until pi-bar has a current update. The `extensions` segment stays hidden when no extension has set a status.
|
|
68
|
+
Allowed segments are `model`, `thinking`, `context`, `cwd`, `progress`, and `extensions`. The `cwd` segment is off by default. The `progress` segment stays hidden until pi-bar has a current update. The `extensions` segment stays hidden when no extension has set a status.
|
|
56
69
|
|
|
57
70
|
You can also set startup defaults with environment variables before launching pi:
|
|
58
71
|
|
|
@@ -61,6 +74,30 @@ PI_BAR_SHOW=model,thinking,context,progress,extensions pi
|
|
|
61
74
|
PI_BAR_SHOW=model,context pi
|
|
62
75
|
```
|
|
63
76
|
|
|
77
|
+
### Show the session directory
|
|
78
|
+
|
|
79
|
+
Enable the optional `cwd` segment to distinguish projects and sessions:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
/bar segments show cwd
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or set startup segments with `PI_BAR_SHOW=model,thinking,context,cwd,progress,extensions`.
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ ~/projects/pi-bar
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Your home directory becomes `~`. Long paths omit middle directories, retaining trailing directory names where possible. The segment is capped at 36 terminal columns, including wide Unicode characters. Override the cap before starting pi:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
PI_BAR_CWD_MAX_WIDTH=48 pi
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The cap must be an integer of at least 8; invalid values use 36. Very long directory names are truncated. Paths are stripped of terminal controls before display.
|
|
98
|
+
|
|
99
|
+
This is Pi's session working directory (`ctx.cwd`), not a live shell directory: `cd` inside a tool command does not change it. Visibility persists through the existing `/bar` configuration.
|
|
100
|
+
|
|
64
101
|
### Configure live progress updates
|
|
65
102
|
|
|
66
103
|
pi-bar shows a short, plain-English description of what pi is working on right now. It refreshes as pi works and resets when you switch branches in the session tree, so stale updates never follow you across tasks. Hide `Progress update` in `/bar`, run `/bar segments hide progress`, or set `PI_BAR_SHOW` without `progress` to disable it.
|
|
@@ -75,7 +112,9 @@ Or in pi settings: `bar.progressModel`. Otherwise pi-bar picks a fast model you
|
|
|
75
112
|
|
|
76
113
|
### Configure extension statuses
|
|
77
114
|
|
|
78
|
-
Other pi extensions can publish small status badges. Pi-bar
|
|
115
|
+
Other pi extensions can publish small status badges. Pi-bar displays their text as written, without prepending internal keys: `setStatus("mcp", "MCP: 2/2 servers")` appears as `MCP: 2/2 servers`, not `mcp:MCP: 2/2 servers`. Extensions should publish self-describing text; pi-bar does not guess labels for bare values.
|
|
116
|
+
|
|
117
|
+
Pi-bar strips embedded terminal colors and control sequences, and separates each badge with the same `❯` divider used by other footer segments. Keys remain available for filtering and identification in `/bar`; selecting a status row shows its current sanitized text. Run `/bar` or `/bar status` inside pi to inspect statuses or pick which ones to show:
|
|
79
118
|
|
|
80
119
|
```text
|
|
81
120
|
/bar status
|
|
@@ -98,6 +137,16 @@ The first number is the warning/yellow threshold. The second number is the dange
|
|
|
98
137
|
- **[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.
|
|
99
138
|
- **[pi-qq](https://www.npmjs.com/package/pi-qq)** — ask side questions about what the agent just did without polluting the transcript.
|
|
100
139
|
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm ci --ignore-scripts
|
|
144
|
+
npm test
|
|
145
|
+
npm run check
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Tests cover path formatting, display-ready statuses, responsive layout, terminal safety, ANSI/Unicode width limits, resize behavior, and segment/status visibility persistence. Test configuration mirrors Pi's `pi-ai/compat` loader alias; runtime dependencies remain optional peers supplied by Pi.
|
|
149
|
+
|
|
101
150
|
## Security note
|
|
102
151
|
|
|
103
152
|
Pi extensions run with your local user permissions. Review any pi package source before installing it.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* pi-bar — footer / statusline extension.
|
|
3
3
|
*
|
|
4
4
|
* Replaces pi's built-in footer with left-aligned segments:
|
|
5
|
-
* <model> ❯ think:<level> ❯ <context% / window> ❯ <progress> ❯ <extensions>
|
|
5
|
+
* <model> ❯ think:<level> ❯ <context% / window> ❯ [cwd] ❯ <progress> ❯ <extensions>
|
|
6
6
|
*
|
|
7
7
|
* Example:
|
|
8
8
|
* claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Reviewing package structure
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
* PI_BAR_THRESHOLDS warning,danger context-usage percentages
|
|
16
16
|
* PI_BAR_PROGRESS_MODEL provider/id for the progress update model
|
|
17
17
|
* PI_BAR_CONFIG override the persisted pi-bar config path
|
|
18
|
+
* PI_BAR_CWD_MAX_WIDTH maximum cwd segment width in terminal columns (default 36)
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
21
22
|
import { homedir } from "node:os";
|
|
22
|
-
import { dirname, join } from "node:path";
|
|
23
|
+
import { dirname, join, posix, win32 } from "node:path";
|
|
23
24
|
import { complete, type UserMessage } from "@earendil-works/pi-ai";
|
|
24
25
|
import {
|
|
25
26
|
getSettingsListTheme,
|
|
@@ -33,9 +34,10 @@ import {
|
|
|
33
34
|
type SettingItem,
|
|
34
35
|
SettingsList,
|
|
35
36
|
truncateToWidth,
|
|
37
|
+
visibleWidth,
|
|
36
38
|
} from "@earendil-works/pi-tui";
|
|
37
39
|
|
|
38
|
-
type SegmentName = "model" | "thinking" | "context" | "progress" | "extensions";
|
|
40
|
+
type SegmentName = "model" | "thinking" | "context" | "cwd" | "progress" | "extensions";
|
|
39
41
|
type StatusFilter =
|
|
40
42
|
| { mode: "all"; hidden: Set<string> }
|
|
41
43
|
| { mode: "only"; shown: Set<string> };
|
|
@@ -75,7 +77,7 @@ type ProgressModelPreference = { provider: string; id: string };
|
|
|
75
77
|
type FastModelAuth = {
|
|
76
78
|
model: Parameters<typeof complete>[0];
|
|
77
79
|
apiKey: string;
|
|
78
|
-
headers?:
|
|
80
|
+
headers?: NonNullable<Parameters<typeof complete>[2]>["headers"];
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
const STATUS_FILTER_ENTRY_TYPE = "pi-bar-status-filter";
|
|
@@ -118,6 +120,7 @@ const ALL_SEGMENTS: readonly SegmentName[] = [
|
|
|
118
120
|
"model",
|
|
119
121
|
"thinking",
|
|
120
122
|
"context",
|
|
123
|
+
"cwd",
|
|
121
124
|
"progress",
|
|
122
125
|
"extensions",
|
|
123
126
|
];
|
|
@@ -125,6 +128,7 @@ const SEGMENT_LABELS: Record<SegmentName, string> = {
|
|
|
125
128
|
model: "Model",
|
|
126
129
|
thinking: "Thinking level",
|
|
127
130
|
context: "Context usage",
|
|
131
|
+
cwd: "Current directory",
|
|
128
132
|
progress: "Progress update",
|
|
129
133
|
extensions: "Extension statuses",
|
|
130
134
|
};
|
|
@@ -132,7 +136,6 @@ const DEFAULT_WARNING_THRESHOLD = 70;
|
|
|
132
136
|
const DEFAULT_ERROR_THRESHOLD = 90;
|
|
133
137
|
|
|
134
138
|
const SEGMENT_SEPARATOR = "❯";
|
|
135
|
-
const EXTENSION_STATUS_SEPARATOR = SEGMENT_SEPARATOR;
|
|
136
139
|
|
|
137
140
|
function formatTokens(n: number): string {
|
|
138
141
|
if (n >= 1_000_000) {
|
|
@@ -152,6 +155,150 @@ function formatModelName(id: string | undefined): string {
|
|
|
152
155
|
return base.replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
const DEFAULT_CWD_MAX_WIDTH = 36;
|
|
159
|
+
|
|
160
|
+
export function parseCwdMaxWidth(raw: string | undefined): number {
|
|
161
|
+
const value = raw?.trim() ?? "";
|
|
162
|
+
const width = /^\d+$/.test(value) ? Number(value) : NaN;
|
|
163
|
+
return Number.isSafeInteger(width) && width >= 8 ? width : DEFAULT_CWD_MAX_WIDTH;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Format the session directory, preserving the leaf when middle components fit poorly. */
|
|
167
|
+
export function formatCwd(
|
|
168
|
+
cwd: string,
|
|
169
|
+
home = homedir(),
|
|
170
|
+
maxWidth = DEFAULT_CWD_MAX_WIDTH,
|
|
171
|
+
paths = process.platform === "win32" ? win32 : posix,
|
|
172
|
+
): string {
|
|
173
|
+
if (maxWidth <= 0) return "";
|
|
174
|
+
if (!cwd) return truncateToWidth("—", maxWidth);
|
|
175
|
+
|
|
176
|
+
const relative = home ? paths.relative(home, cwd) : undefined;
|
|
177
|
+
const insideHome = relative !== undefined &&
|
|
178
|
+
!paths.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${paths.sep}`);
|
|
179
|
+
const prefix = insideHome ? `~${paths.sep}` : paths.parse(cwd).root;
|
|
180
|
+
const tail = insideHome ? relative! : cwd.slice(prefix.length);
|
|
181
|
+
const display = stripTerminalControls(insideHome ? (tail ? `${prefix}${tail}` : "~") : cwd);
|
|
182
|
+
if (visibleWidth(display) <= maxWidth) return display;
|
|
183
|
+
|
|
184
|
+
const parts = tail.split(paths.sep).filter(Boolean);
|
|
185
|
+
if (parts.length < 2) return truncateToWidth(display, maxWidth);
|
|
186
|
+
|
|
187
|
+
// Keep as many trailing directories as the column budget allows.
|
|
188
|
+
let compact = stripTerminalControls(`${prefix}…${paths.sep}${parts.at(-1)}`);
|
|
189
|
+
for (let index = parts.length - 2; index > 0; index--) {
|
|
190
|
+
const candidate = stripTerminalControls(`${prefix}…${paths.sep}${parts.slice(index).join(paths.sep)}`);
|
|
191
|
+
if (visibleWidth(candidate) > maxWidth) break;
|
|
192
|
+
compact = candidate;
|
|
193
|
+
}
|
|
194
|
+
return truncateToWidth(compact, maxWidth);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Preferred directory display, then parent/project, then project only. */
|
|
198
|
+
export function cwdVariants(
|
|
199
|
+
cwd: string,
|
|
200
|
+
home = homedir(),
|
|
201
|
+
maxWidth = DEFAULT_CWD_MAX_WIDTH,
|
|
202
|
+
paths = process.platform === "win32" ? win32 : posix,
|
|
203
|
+
): string[] {
|
|
204
|
+
const safeCwd = stripTerminalControls(cwd);
|
|
205
|
+
const leaf = paths.basename(safeCwd);
|
|
206
|
+
const parent = paths.basename(paths.dirname(safeCwd));
|
|
207
|
+
return [
|
|
208
|
+
formatCwd(cwd, home, maxWidth, paths),
|
|
209
|
+
...[parent && leaf ? `${parent}${paths.sep}${leaf}` : leaf, leaf]
|
|
210
|
+
.filter(Boolean)
|
|
211
|
+
.map((text) => truncateToWidth(stripTerminalControls(text), maxWidth, "…")),
|
|
212
|
+
].map(stripTerminalControls);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type FooterSegment = {
|
|
216
|
+
name: SegmentName;
|
|
217
|
+
text: string;
|
|
218
|
+
/** Shorter, already styled representations, in preference order. */
|
|
219
|
+
alternatives?: readonly string[];
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/** Pure, column-aware layout. The divider may be styled but has no padding. Never split a status badge. */
|
|
223
|
+
export function layoutFooter(
|
|
224
|
+
segments: readonly FooterSegment[],
|
|
225
|
+
badges: readonly string[],
|
|
226
|
+
width: number,
|
|
227
|
+
divider = SEGMENT_SEPARATOR,
|
|
228
|
+
): string {
|
|
229
|
+
if (!Number.isFinite(width) || width <= 0) return "";
|
|
230
|
+
width = Math.floor(width);
|
|
231
|
+
let separator = ` ${divider} `;
|
|
232
|
+
const items = segments.map((segment) => ({ ...segment }));
|
|
233
|
+
const extensions = items.find((item) => item.name === "extensions");
|
|
234
|
+
let shownBadges = badges.length;
|
|
235
|
+
const updateBadges = () => {
|
|
236
|
+
if (!extensions) return;
|
|
237
|
+
const hidden = badges.length - shownBadges;
|
|
238
|
+
extensions.text = [...badges.slice(0, shownBadges), ...(hidden ? [`+${hidden}`] : [])].join(separator);
|
|
239
|
+
};
|
|
240
|
+
updateBadges();
|
|
241
|
+
const line = () => items.map((item) => item.text).filter(Boolean).join(separator);
|
|
242
|
+
const overflow = () => visibleWidth(line()) - width;
|
|
243
|
+
const progress = items.find((item) => item.name === "progress");
|
|
244
|
+
if (progress && overflow() > 0) {
|
|
245
|
+
const currentWidth = visibleWidth(progress.text);
|
|
246
|
+
// Keep a short activity fragment while trying the other compact forms.
|
|
247
|
+
const minimumWidth = Math.min(12, width);
|
|
248
|
+
const progressWidth = Math.min(currentWidth, Math.max(minimumWidth, currentWidth - overflow()));
|
|
249
|
+
progress.text = truncateToWidth(progress.text, progressWidth, "…");
|
|
250
|
+
}
|
|
251
|
+
// Keep the roomy default while progress alone can absorb width pressure.
|
|
252
|
+
// Tighten gaps before compacting paths/context or overflowing whole badges.
|
|
253
|
+
if (overflow() > 0) {
|
|
254
|
+
separator = ` ${divider} `;
|
|
255
|
+
updateBadges();
|
|
256
|
+
}
|
|
257
|
+
for (const name of ["cwd", "context", "thinking"] as const) {
|
|
258
|
+
const item = items.find((item) => item.name === name);
|
|
259
|
+
if (!item) continue;
|
|
260
|
+
for (const alternative of item.alternatives ?? []) {
|
|
261
|
+
if (overflow() <= 0) break;
|
|
262
|
+
if (visibleWidth(alternative) < visibleWidth(item.text)) item.text = alternative;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
while (extensions && shownBadges > 0 && overflow() > 0) {
|
|
266
|
+
shownBadges--;
|
|
267
|
+
updateBadges();
|
|
268
|
+
}
|
|
269
|
+
// On very narrow terminals, remove optional content before clipping identity.
|
|
270
|
+
for (const name of ["progress", "cwd", "extensions"] as const) {
|
|
271
|
+
if (overflow() <= 0) break;
|
|
272
|
+
const item = items.find((item) => item.name === name);
|
|
273
|
+
if (item) {
|
|
274
|
+
item.text = name === "cwd"
|
|
275
|
+
? truncateToWidth(item.text, Math.max(0, visibleWidth(item.text) - overflow()), "…")
|
|
276
|
+
: "";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const model = items.find((item) => item.name === "model");
|
|
280
|
+
if (model && overflow() > 0) {
|
|
281
|
+
model.text = truncateToWidth(model.text, Math.max(0, visibleWidth(model.text) - overflow()), "…");
|
|
282
|
+
}
|
|
283
|
+
// Whole-badge overflow can free more space than needed. Use that space rather
|
|
284
|
+
// than leaving unnecessarily compact context, paths, or progress on screen.
|
|
285
|
+
for (const name of ["context", "thinking", "cwd", "progress"] as const) {
|
|
286
|
+
const item = items.find((item) => item.name === name);
|
|
287
|
+
const original = segments.find((segment) => segment.name === name);
|
|
288
|
+
if (!item?.text || !original) continue;
|
|
289
|
+
const budget = visibleWidth(item.text) - overflow();
|
|
290
|
+
if (name === "progress") {
|
|
291
|
+
item.text = truncateToWidth(original.text, Math.max(0, budget), "…");
|
|
292
|
+
} else {
|
|
293
|
+
const preferred = [original.text, ...(original.alternatives ?? [])]
|
|
294
|
+
.find((text) => visibleWidth(text) <= budget);
|
|
295
|
+
if (preferred !== undefined) item.text = preferred;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// A terminal narrower than thinking + context cannot show every core value.
|
|
299
|
+
return truncateToWidth(line(), width, "…");
|
|
300
|
+
}
|
|
301
|
+
|
|
155
302
|
function thinkingColor(level: string): ThemeColor {
|
|
156
303
|
switch (level) {
|
|
157
304
|
case "off":
|
|
@@ -1523,7 +1670,7 @@ function shouldShowStatus(key: string, filter: StatusFilter): boolean {
|
|
|
1523
1670
|
return !filter.hidden.has(key);
|
|
1524
1671
|
}
|
|
1525
1672
|
|
|
1526
|
-
function formatExtensionStatuses(
|
|
1673
|
+
export function formatExtensionStatuses(
|
|
1527
1674
|
statuses: ReadonlyMap<string, string>,
|
|
1528
1675
|
filter: StatusFilter,
|
|
1529
1676
|
seenStatusKeys: Set<string>,
|
|
@@ -1534,9 +1681,7 @@ function formatExtensionStatuses(
|
|
|
1534
1681
|
seenStatusKeys.add(key);
|
|
1535
1682
|
return shouldShowStatus(key, filter);
|
|
1536
1683
|
})
|
|
1537
|
-
.map(([
|
|
1538
|
-
`${stripTerminalControls(key)}:${stripTerminalControls(text)}`,
|
|
1539
|
-
);
|
|
1684
|
+
.map(([, text]) => stripTerminalControls(text));
|
|
1540
1685
|
|
|
1541
1686
|
return parts.length > 0 ? parts : null;
|
|
1542
1687
|
}
|
|
@@ -1656,6 +1801,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1656
1801
|
let statusFilter: StatusFilter = { mode: "all", hidden: new Set() };
|
|
1657
1802
|
let visibleSegments: SegmentName[] = readGlobalSegments() ?? DEFAULT_SEGMENTS;
|
|
1658
1803
|
const seenStatusKeys = new Set<string>();
|
|
1804
|
+
let currentStatuses: ReadonlyMap<string, string> = new Map();
|
|
1805
|
+
const statusDescription = (key: string) =>
|
|
1806
|
+
stripTerminalControls(currentStatuses.get(key) ?? "") || "No current status text";
|
|
1659
1807
|
const refresh = () => requestRender?.();
|
|
1660
1808
|
const progress = new FooterProgressEngine(refresh);
|
|
1661
1809
|
const restoreStatusFilter = (ctx: ExtensionContext) => {
|
|
@@ -1740,8 +1888,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1740
1888
|
},
|
|
1741
1889
|
...knownStatusKeys.map((key): SettingItem => ({
|
|
1742
1890
|
id: `status:${key}`,
|
|
1743
|
-
label: `Status: ${key}`,
|
|
1744
|
-
description:
|
|
1891
|
+
label: `Status: ${stripTerminalControls(key)}`,
|
|
1892
|
+
description: statusDescription(key),
|
|
1745
1893
|
currentValue: statusVisibility.get(key) ? "shown" : "hidden",
|
|
1746
1894
|
values: ["shown", "hidden"],
|
|
1747
1895
|
})),
|
|
@@ -1856,8 +2004,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1856
2004
|
},
|
|
1857
2005
|
...knownStatusKeys.map((key): SettingItem => ({
|
|
1858
2006
|
id: key,
|
|
1859
|
-
label: key,
|
|
1860
|
-
description:
|
|
2007
|
+
label: stripTerminalControls(key),
|
|
2008
|
+
description: statusDescription(key),
|
|
1861
2009
|
currentValue: statusVisibility.get(key) ? "shown" : "hidden",
|
|
1862
2010
|
values: ["shown", "hidden"],
|
|
1863
2011
|
})),
|
|
@@ -2078,6 +2226,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2078
2226
|
if (!ctx.hasUI) return;
|
|
2079
2227
|
|
|
2080
2228
|
const { warningThreshold, errorThreshold } = parseThresholds();
|
|
2229
|
+
const cwdWidth = parseCwdMaxWidth(process.env.PI_BAR_CWD_MAX_WIDTH);
|
|
2081
2230
|
|
|
2082
2231
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
2083
2232
|
requestRender = () => tui.requestRender();
|
|
@@ -2090,8 +2239,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
2090
2239
|
render(width: number): string[] {
|
|
2091
2240
|
const modelName = formatModelName(ctx.model?.id);
|
|
2092
2241
|
const thinkingLevel = String(pi.getThinkingLevel());
|
|
2242
|
+
currentStatuses = footerData?.getExtensionStatuses?.() ?? new Map();
|
|
2093
2243
|
const extensionStatusParts = formatExtensionStatuses(
|
|
2094
|
-
|
|
2244
|
+
currentStatuses,
|
|
2095
2245
|
statusFilter,
|
|
2096
2246
|
seenStatusKeys,
|
|
2097
2247
|
);
|
|
@@ -2107,26 +2257,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
2107
2257
|
: "—";
|
|
2108
2258
|
const progressText = progress.text();
|
|
2109
2259
|
|
|
2110
|
-
const
|
|
2111
|
-
?
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
:
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2260
|
+
const directories = visibleSegments.includes("cwd")
|
|
2261
|
+
? cwdVariants(ctx.cwd, homedir(), cwdWidth).map((text) => theme.fg("accent", text))
|
|
2262
|
+
: [];
|
|
2263
|
+
const contextCompact = usage
|
|
2264
|
+
? usage.percent !== null ? `${usage.percent.toFixed(1)}%` : "—%"
|
|
2265
|
+
: "—";
|
|
2266
|
+
const shortThinking = thinkingLevel === "medium" ? "med" : thinkingLevel === "minimal" ? "min" : thinkingLevel;
|
|
2267
|
+
const renderers: Record<SegmentName, Omit<FooterSegment, "name">> = {
|
|
2268
|
+
model: { text: theme.fg("accent", stripTerminalControls(modelName)) },
|
|
2269
|
+
thinking: {
|
|
2270
|
+
text: theme.fg(thinkingColor(thinkingLevel), `think:${thinkingLevel}`),
|
|
2271
|
+
alternatives: [theme.fg(thinkingColor(thinkingLevel), `think:${shortThinking}`)],
|
|
2272
|
+
},
|
|
2273
|
+
context: {
|
|
2274
|
+
text: theme.fg(contextSegmentColor, contextText),
|
|
2275
|
+
alternatives: [theme.fg(contextSegmentColor, contextCompact)],
|
|
2276
|
+
},
|
|
2277
|
+
cwd: { text: directories[0] ?? "", alternatives: directories.slice(1) },
|
|
2278
|
+
progress: { text: progressText ? theme.fg("text", progressText) : "" },
|
|
2279
|
+
extensions: { text: "" },
|
|
2121
2280
|
};
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
.
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
return [truncateToWidth(line, width)];
|
|
2281
|
+
return [layoutFooter(
|
|
2282
|
+
visibleSegments.map((name) => ({ name, ...renderers[name] })),
|
|
2283
|
+
(extensionStatusParts ?? []).map((part) => theme.fg("text", part)),
|
|
2284
|
+
width,
|
|
2285
|
+
theme.fg("dim", SEGMENT_SEPARATOR),
|
|
2286
|
+
)];
|
|
2130
2287
|
},
|
|
2131
2288
|
};
|
|
2132
2289
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.43",
|
|
4
4
|
"description": "Pi footer/statusline showing active model, thinking level, context pressure, live progress updates, and extension statuses.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -52,5 +52,17 @@
|
|
|
52
52
|
"./extensions/status-footer.ts"
|
|
53
53
|
],
|
|
54
54
|
"image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.38/assets/screenshot-yellow.png"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"test": "tsx --tsconfig tests/tsconfig.json --test tests/*.test.ts",
|
|
58
|
+
"check": "tsc -p tests/tsconfig.json"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@earendil-works/pi-ai": "^0.85.1",
|
|
62
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
63
|
+
"@earendil-works/pi-tui": "^0.85.1",
|
|
64
|
+
"@types/node": "^22.20.2",
|
|
65
|
+
"tsx": "^4.23.13",
|
|
66
|
+
"typescript": "^5.9.3"
|
|
55
67
|
}
|
|
56
68
|
}
|