pi-bar 0.3.44 → 0.3.45
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 +39 -19
- package/extensions/status-footer.ts +257 -272
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ The footer adapts to terminal width without extra configuration:
|
|
|
43
43
|
- Separators have two spaces on each side by default, for a roomier display.
|
|
44
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. An enabled provider prefix can yield before the model name is clipped.
|
|
45
45
|
- Optional token totals, cost, and cache-hit ratio yield whole under width pressure, before overflowing extension badges. Numeric metrics are never shown partially.
|
|
46
|
-
- 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
|
|
46
|
+
- 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 badges` to inspect their text and visibility by key.
|
|
47
47
|
- 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.
|
|
48
48
|
- Widening the terminal restores the full display. Thinking and context retain their semantic colors throughout.
|
|
49
49
|
|
|
@@ -51,24 +51,42 @@ Existing visibility settings still apply. CWD, usage metrics, and the provider p
|
|
|
51
51
|
|
|
52
52
|
## Customization
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
**Start with `/bar`.** It opens all footer settings; you do not need to learn command groups.
|
|
55
55
|
|
|
56
56
|
```text
|
|
57
57
|
/bar
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Type to find a setting, then press Enter/Space to change it. Changes save immediately; Esc closes the screen rather than undoing them.
|
|
61
|
+
|
|
62
|
+
Related settings stay together:
|
|
63
|
+
- **Model → Show provider:** model visibility and its optional provider prefix.
|
|
64
|
+
- **Progress update → Progress model:** progress visibility and a searchable model picker.
|
|
65
|
+
- **Extension badges → Choose badges:** overall badge visibility and a separate screen for individual badges. Badge lists do not crowd the main settings panel. Esc from that screen returns to settings.
|
|
66
|
+
|
|
67
|
+
For quick changes, Tab offers action-oriented commands with short explanations:
|
|
68
|
+
|
|
69
|
+
| Command | Purpose |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `/bar settings` | Open all settings, just like `/bar`. |
|
|
72
|
+
| `/bar show <items>` | Show footer items. |
|
|
73
|
+
| `/bar hide <items>` | Hide footer items. |
|
|
74
|
+
| `/bar badges` | Choose badges published by other extensions. |
|
|
75
|
+
| `/bar provider [show\|hide]` | Show, hide, or inspect the provider prefix. |
|
|
76
|
+
| `/bar progress-model [auto\|provider/model]` | Choose or inspect the progress model. |
|
|
77
|
+
| `/bar help` | Show commands, examples, and current visibility settings. |
|
|
61
78
|
|
|
62
79
|
```text
|
|
63
|
-
/bar
|
|
64
|
-
/bar
|
|
65
|
-
/bar
|
|
66
|
-
/bar segments show thinking
|
|
80
|
+
/bar hide progress
|
|
81
|
+
/bar show cost tokens
|
|
82
|
+
/bar badges hide mcp
|
|
67
83
|
```
|
|
68
84
|
|
|
69
|
-
|
|
85
|
+
Footer item names are `model`, `thinking`, `context`, `cache_hit_ratio`, `cost`, `tokens`, `cwd`, `progress`, and `extensions`. Use `/bar show all` or `/bar hide all` to change all footer items at once. `cwd`, `cache_hit_ratio`, `cost`, and `tokens` are off by default. Enabled items can still be invisible while waiting for data or when terminal space is tight.
|
|
86
|
+
|
|
87
|
+
Tab completes item names, badge keys, and configured progress-model IDs while preserving preceding arguments, including comma-separated lists such as `/bar show model,ca`.
|
|
70
88
|
|
|
71
|
-
|
|
89
|
+
**Existing commands still work:** `/bar config`, `/bar segments ...`, `/bar status ...`, and `/bar list` are compatibility aliases, not additional choices in the top-level menu. `/bar list` and `/bar segments list` only report enabled footer items. Advanced commands such as `/bar segments only model context` remain available.
|
|
72
90
|
|
|
73
91
|
You can also set startup defaults with environment variables before launching pi:
|
|
74
92
|
|
|
@@ -82,7 +100,7 @@ PI_BAR_SHOW=model,context pi
|
|
|
82
100
|
Enable the optional `cwd` segment to distinguish projects and sessions:
|
|
83
101
|
|
|
84
102
|
```text
|
|
85
|
-
/bar
|
|
103
|
+
/bar show cwd
|
|
86
104
|
```
|
|
87
105
|
|
|
88
106
|
Or set startup segments with `PI_BAR_SHOW=model,thinking,context,cwd,progress,extensions`.
|
|
@@ -123,10 +141,10 @@ The prefix uses the actual model provider, not a namespace embedded in the model
|
|
|
123
141
|
|
|
124
142
|
### Show cache, cost, and token metrics
|
|
125
143
|
|
|
126
|
-
Enable any of the optional metrics through `/bar` or the
|
|
144
|
+
Enable any of the optional metrics through `/bar` or the quick visibility commands:
|
|
127
145
|
|
|
128
146
|
```text
|
|
129
|
-
/bar
|
|
147
|
+
/bar show cache_hit_ratio cost tokens
|
|
130
148
|
```
|
|
131
149
|
|
|
132
150
|
```text
|
|
@@ -135,7 +153,7 @@ claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ CH:84% ❯ ≈$0.123
|
|
|
135
153
|
|
|
136
154
|
| Segment | Meaning |
|
|
137
155
|
| --- | --- |
|
|
138
|
-
| `cache_hit_ratio` | Latest active-branch assistant prompt's cache-read percentage: `cacheRead / (input + cacheRead + cacheWrite)`. Hidden when that response has no reported prompt usage. Restored on resume and `/tree`; tool, summary, and progress calls do not replace this value. |
|
|
156
|
+
| `cache_hit_ratio` | Latest active-branch assistant prompt's cache-read percentage: `cacheRead / (input + cacheRead + cacheWrite)`. Hidden when that response has no reported prompt usage. Colored green at 90% or higher, yellow from 50% up to 90%, and red below 50% (mostly cache misses). Restored on resume and `/tree`; tool, summary, and progress calls do not replace this value. |
|
|
139
157
|
| `cost` | Estimated dollar cost for recorded usage in the current session file. `≈` means an estimate, **not an invoice**. |
|
|
140
158
|
| `tokens` | Cumulative input (`↑`, including cache reads and writes) and output (`↓`, including reported reasoning tokens). These are usage totals, not current context size or tokens/sec. |
|
|
141
159
|
|
|
@@ -147,7 +165,7 @@ Prices come from the usage estimates Pi/providers report. Subscription allowance
|
|
|
147
165
|
|
|
148
166
|
### Configure live progress updates
|
|
149
167
|
|
|
150
|
-
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
|
|
168
|
+
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 hide progress`, or set `PI_BAR_SHOW` without `progress` to disable it.
|
|
151
169
|
|
|
152
170
|
**Pick a model interactively:** open `/bar`, select `Progress model` directly below `Progress update`, and press Enter. Type a provider or model ID (for example `gpt`), then press Enter to select. Esc cancels without changing the preference. The picker shows models with configured Pi credentials and an `Auto` option; it does not validate keys or make model requests.
|
|
153
171
|
|
|
@@ -173,17 +191,19 @@ PI_BAR_PROGRESS_MODEL=openai/gpt-4.1-mini pi
|
|
|
173
191
|
|
|
174
192
|
Preference order: `PI_BAR_PROGRESS_MODEL` → saved pi-bar `progressModel` → project Pi settings → global Pi settings → Auto. Within each Pi settings scope, `bar.progressModel` takes precedence over the legacy `progress.model` setting. A nonempty environment override makes both the picker and command read-only and suppresses model-value completion; unset it and restart Pi to choose another model. A saved `"auto"` explicitly enables automatic selection rather than falling back to Pi settings. Remove `progressModel` from the pi-bar config to use Pi settings again.
|
|
175
193
|
|
|
176
|
-
### Configure extension
|
|
194
|
+
### Configure extension badges
|
|
177
195
|
|
|
178
196
|
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.
|
|
179
197
|
|
|
180
|
-
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
|
|
198
|
+
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. Open `/bar` → `Choose badges`, or use the shortcut:
|
|
181
199
|
|
|
182
200
|
```text
|
|
183
|
-
/bar
|
|
201
|
+
/bar badges
|
|
184
202
|
```
|
|
185
203
|
|
|
186
|
-
Toggle
|
|
204
|
+
Each `Badge: <key>` row shows its current sanitized text when selected. Toggle badges between `shown` and `hidden`; the `New badges` row controls the default for badges discovered later without changing existing choices. An empty screen explains that extensions have not published badges yet, while still letting you set that default.
|
|
205
|
+
|
|
206
|
+
These choices do not enable a hidden `Extension badges` footer item. Use `/bar show extensions` to display the badge area. `/bar badges show <keys>` and `/bar badges hide <keys>` are command shortcuts; the older `/bar status ...` spellings also work.
|
|
187
207
|
|
|
188
208
|
Your choices persist across pi sessions in `~/.pi/agent/pi-bar.json`. Override the path with `PI_BAR_CONFIG=/some/path.json`.
|
|
189
209
|
|
|
@@ -208,7 +228,7 @@ npm test
|
|
|
208
228
|
npm run check
|
|
209
229
|
```
|
|
210
230
|
|
|
211
|
-
Tests cover path formatting, display-ready statuses, responsive layout, all 512 segment visibility combinations, terminal safety, ANSI/Unicode widths, provider settings, progress-model selection, command completion, usage accounting, progress-call persistence, session/tree lifecycle, and config persistence. Test configuration mirrors Pi's `pi-ai/compat` loader alias; runtime dependencies remain optional peers supplied by Pi.
|
|
231
|
+
Tests cover path formatting, display-ready statuses, responsive layout, all 512 segment visibility combinations, terminal safety, ANSI/Unicode widths, provider settings, progress-model selection, menu navigation, command completion and legacy aliases, usage accounting, progress-call persistence, session/tree lifecycle, and config persistence. Test configuration mirrors Pi's `pi-ai/compat` loader alias; runtime dependencies remain optional peers supplied by Pi.
|
|
212
232
|
|
|
213
233
|
## Security note
|
|
214
234
|
|
|
@@ -140,8 +140,38 @@ const SEGMENT_LABELS: Record<SegmentName, string> = {
|
|
|
140
140
|
tokens: "Session token totals",
|
|
141
141
|
cwd: "Current directory",
|
|
142
142
|
progress: "Progress update",
|
|
143
|
-
extensions: "Extension
|
|
143
|
+
extensions: "Extension badges",
|
|
144
144
|
};
|
|
145
|
+
const SEGMENT_DESCRIPTIONS: Record<SegmentName, string> = {
|
|
146
|
+
model: "Show the current chat model's name. This does not change your model.",
|
|
147
|
+
thinking: "Show the current thinking level, with its usual color.",
|
|
148
|
+
context: "Show context usage and the model's context window.",
|
|
149
|
+
cache_hit_ratio: "Show the latest response's prompt cache-hit rate. Hidden until prompt usage is available.",
|
|
150
|
+
cost: "Show estimated recorded session cost, not an invoice. Hidden until usage is available.",
|
|
151
|
+
tokens: "Show recorded input and output token totals, including cache usage.",
|
|
152
|
+
cwd: "Show this session's directory, not the directory of a tool's shell.",
|
|
153
|
+
progress: "Show live activity updates. Uses extra model calls; choose the progress model below.",
|
|
154
|
+
extensions: "Show badges published by other extensions. Choose individual badges below.",
|
|
155
|
+
};
|
|
156
|
+
// Advertise tasks, not the legacy command groups. Old spellings still work.
|
|
157
|
+
const BAR_COMMAND_DESCRIPTIONS = new Map([
|
|
158
|
+
["settings", "Open all footer settings"],
|
|
159
|
+
["show", "Show footer items"],
|
|
160
|
+
["hide", "Hide footer items"],
|
|
161
|
+
["badges", "Choose extension badges"],
|
|
162
|
+
["provider", "Show or hide provider prefix"],
|
|
163
|
+
["progress-model", "Choose progress model"],
|
|
164
|
+
["help", "Commands and examples"],
|
|
165
|
+
]);
|
|
166
|
+
const BAR_ACTION_DESCRIPTIONS = new Map([
|
|
167
|
+
["show", "Show selected items"],
|
|
168
|
+
["hide", "Hide selected items"],
|
|
169
|
+
["only", "Show only selected items"],
|
|
170
|
+
["all", "Show all items"],
|
|
171
|
+
["none", "Hide all items"],
|
|
172
|
+
["list", "Inspect visibility without changing it"],
|
|
173
|
+
["settings", "Open visibility settings"],
|
|
174
|
+
]);
|
|
145
175
|
const DEFAULT_WARNING_THRESHOLD = 70;
|
|
146
176
|
const DEFAULT_ERROR_THRESHOLD = 90;
|
|
147
177
|
|
|
@@ -357,6 +387,19 @@ function contextColor(
|
|
|
357
387
|
return "success";
|
|
358
388
|
}
|
|
359
389
|
|
|
390
|
+
// Cache hit ratio is "higher is better", the inverse of context usage. A healthy
|
|
391
|
+
// multi-turn conversation typically reads 90%+ of its prompt from cache; below
|
|
392
|
+
// 50% means most of the prompt was billed as fresh input (a mostly-miss turn).
|
|
393
|
+
const CACHE_HIT_GOOD_THRESHOLD = 90;
|
|
394
|
+
const CACHE_HIT_POOR_THRESHOLD = 50;
|
|
395
|
+
|
|
396
|
+
function cacheHitColor(percent: number | null | undefined): ThemeColor {
|
|
397
|
+
if (percent === null || percent === undefined) return "muted";
|
|
398
|
+
if (percent >= CACHE_HIT_GOOD_THRESHOLD) return "success";
|
|
399
|
+
if (percent >= CACHE_HIT_POOR_THRESHOLD) return "warning";
|
|
400
|
+
return "error";
|
|
401
|
+
}
|
|
402
|
+
|
|
360
403
|
function isSegmentName(value: string): value is SegmentName {
|
|
361
404
|
return (ALL_SEGMENTS as readonly string[]).includes(value);
|
|
362
405
|
}
|
|
@@ -1834,11 +1877,11 @@ function describeSegments(segments: readonly SegmentName[]): string {
|
|
|
1834
1877
|
function describeStatusFilter(filter: StatusFilter): string {
|
|
1835
1878
|
if (filter.mode === "only") {
|
|
1836
1879
|
const shown = Array.from(filter.shown).sort();
|
|
1837
|
-
return shown.length > 0 ? `showing only: ${shown.join(", ")}` : "showing none";
|
|
1880
|
+
return shown.length > 0 ? `showing only: ${shown.map(stripTerminalControls).join(", ")}` : "showing none";
|
|
1838
1881
|
}
|
|
1839
1882
|
|
|
1840
1883
|
const hidden = Array.from(filter.hidden).sort();
|
|
1841
|
-
return hidden.length > 0 ? `showing all except: ${hidden.join(", ")}` : "showing all";
|
|
1884
|
+
return hidden.length > 0 ? `showing all except: ${hidden.map(stripTerminalControls).join(", ")}` : "showing all";
|
|
1842
1885
|
}
|
|
1843
1886
|
|
|
1844
1887
|
function readGlobalConfig(): GlobalBarConfig {
|
|
@@ -1894,15 +1937,17 @@ export function completeBarArguments(
|
|
|
1894
1937
|
let candidates: readonly string[];
|
|
1895
1938
|
let selected: string[] = [];
|
|
1896
1939
|
let segmentValues = false;
|
|
1940
|
+
let badgeValues = false;
|
|
1897
1941
|
let modelValues = false;
|
|
1898
1942
|
const section = words[0];
|
|
1943
|
+
const directVisibility = section === "show" || section === "hide";
|
|
1899
1944
|
const segmentSection = section === "segments" || section === "segment" || section === "footer";
|
|
1900
|
-
const statusSection = section === "status" || section === "statuses";
|
|
1945
|
+
const statusSection = section === "badges" || section === "status" || section === "statuses";
|
|
1901
1946
|
|
|
1902
1947
|
if (words.length === 1) {
|
|
1903
|
-
candidates = [
|
|
1948
|
+
candidates = [...BAR_COMMAND_DESCRIPTIONS.keys()];
|
|
1904
1949
|
} else if (words.length === 2 && (segmentSection || statusSection)) {
|
|
1905
|
-
candidates = [
|
|
1950
|
+
candidates = [...BAR_ACTION_DESCRIPTIONS.keys()];
|
|
1906
1951
|
} else if (words.length === 2 && section === "provider") {
|
|
1907
1952
|
candidates = ["show", "hide"];
|
|
1908
1953
|
} else if (words.length === 2 && section === "progress-model") {
|
|
@@ -1910,22 +1955,34 @@ export function completeBarArguments(
|
|
|
1910
1955
|
candidates = ["auto", ...[...new Set(progressModelKeys.filter(
|
|
1911
1956
|
(key) => typeof key === "string" && key !== "auto" && parseSerializedProgressModel(key) === key,
|
|
1912
1957
|
))].sort()];
|
|
1913
|
-
} else if ((segmentSection || statusSection) && ["only", "show", "hide"].includes(words[1])) {
|
|
1958
|
+
} else if (directVisibility || ((segmentSection || statusSection) && ["only", "show", "hide"].includes(words[1]))) {
|
|
1914
1959
|
const comma = fragment.lastIndexOf(",");
|
|
1915
1960
|
const priorValues = fragment.slice(0, comma + 1);
|
|
1916
1961
|
stem += priorValues;
|
|
1917
1962
|
fragment = fragment.slice(comma + 1);
|
|
1918
|
-
selected = [...words.slice(2, -1), priorValues].join(" ").split(/[\s,]+/).filter(Boolean);
|
|
1919
|
-
segmentValues = segmentSection;
|
|
1963
|
+
selected = [...words.slice(directVisibility ? 1 : 2, -1), priorValues].join(" ").split(/[\s,]+/).filter(Boolean);
|
|
1964
|
+
segmentValues = directVisibility || segmentSection;
|
|
1965
|
+
badgeValues = !segmentValues;
|
|
1920
1966
|
// Keys containing delimiters/controls are still configurable in the UI,
|
|
1921
1967
|
// but cannot be represented safely by the existing command grammar.
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1968
|
+
if (directVisibility && selected.some((value) => value.toLowerCase() === "all")) return null;
|
|
1969
|
+
candidates = segmentValues
|
|
1970
|
+
? [...(directVisibility && selected.length === 0 ? ["all"] : []), ...ALL_SEGMENTS]
|
|
1971
|
+
: [...new Set(knownStatusKeys)].filter(
|
|
1972
|
+
(key) => typeof key === "string" && key.length > 0 && !/[\s,\x00-\x1f\x7f-\x9f]/u.test(key),
|
|
1973
|
+
).sort();
|
|
1925
1974
|
} else {
|
|
1926
1975
|
return null;
|
|
1927
1976
|
}
|
|
1928
1977
|
|
|
1978
|
+
const description = (value: string): string | undefined => {
|
|
1979
|
+
if (words.length === 1) return BAR_COMMAND_DESCRIPTIONS.get(value);
|
|
1980
|
+
if (segmentValues) return value === "all" ? "All footer items" : isSegmentName(value) ? SEGMENT_LABELS[value] : undefined;
|
|
1981
|
+
if (badgeValues) return "Extension badge";
|
|
1982
|
+
if (modelValues) return value === "auto" ? "Automatic fast-model selection" : "Progress model";
|
|
1983
|
+
if (section === "provider") return `${value === "show" ? "Show" : "Hide"} provider prefix`;
|
|
1984
|
+
return BAR_ACTION_DESCRIPTIONS.get(value);
|
|
1985
|
+
};
|
|
1929
1986
|
const normalize = (value: string) => segmentValues || modelValues ? value.toLowerCase() : value;
|
|
1930
1987
|
const used = new Set(selected.map(normalize));
|
|
1931
1988
|
const query = normalize(fragment);
|
|
@@ -1934,13 +1991,7 @@ export function completeBarArguments(
|
|
|
1934
1991
|
const normalized = normalize(value);
|
|
1935
1992
|
return (modelValues ? normalized.includes(query) : normalized.startsWith(query)) && !used.has(normalized);
|
|
1936
1993
|
})
|
|
1937
|
-
.map((value) => ({
|
|
1938
|
-
value: `${stem}${value}`,
|
|
1939
|
-
label: value,
|
|
1940
|
-
description: segmentValues && isSegmentName(value) ? SEGMENT_LABELS[value]
|
|
1941
|
-
: modelValues ? value === "auto" ? "Automatic fast-model selection" : "Progress model"
|
|
1942
|
-
: undefined,
|
|
1943
|
-
}));
|
|
1994
|
+
.map((value) => ({ value: `${stem}${value}`, label: value, description: description(value) }));
|
|
1944
1995
|
return matches.length > 0 ? matches : null;
|
|
1945
1996
|
}
|
|
1946
1997
|
|
|
@@ -2035,265 +2086,167 @@ export default function (pi: ExtensionAPI) {
|
|
|
2035
2086
|
if (!previousProgressVisible && nextProgressVisible && ctx) progress.startSession(ctx.cwd);
|
|
2036
2087
|
refresh();
|
|
2037
2088
|
};
|
|
2038
|
-
const
|
|
2089
|
+
const setBadgeVisibility = (key: string, shown: boolean) => {
|
|
2090
|
+
if (statusFilter.mode === "all") {
|
|
2091
|
+
if (shown) statusFilter.hidden.delete(key);
|
|
2092
|
+
else statusFilter.hidden.add(key);
|
|
2093
|
+
} else {
|
|
2094
|
+
if (shown) statusFilter.shown.add(key);
|
|
2095
|
+
else statusFilter.shown.delete(key);
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
const badgeSummary = () => {
|
|
2099
|
+
const keys = getKnownStatusKeys(statusFilter, seenStatusKeys);
|
|
2100
|
+
const enabled = keys.filter((key) => shouldShowStatus(key, statusFilter)).length;
|
|
2101
|
+
return keys.length > 0 ? `${enabled}/${keys.length} enabled` : "No badges yet";
|
|
2102
|
+
};
|
|
2103
|
+
const savedSettingsTheme = (exit: "close" | "go back") => {
|
|
2104
|
+
const theme = getSettingsListTheme();
|
|
2105
|
+
return {
|
|
2106
|
+
...theme,
|
|
2107
|
+
// These toggles save immediately; Esc is not an undo operation.
|
|
2108
|
+
hint: (text: string) => theme.hint(text.replace("Esc to cancel", `Esc to ${exit}`)),
|
|
2109
|
+
};
|
|
2110
|
+
};
|
|
2111
|
+
const createBadgeSettings = (onClose: () => void, exit: "close" | "go back") => {
|
|
2112
|
+
const knownKeys = getKnownStatusKeys(statusFilter, seenStatusKeys);
|
|
2113
|
+
const theme = savedSettingsTheme(exit);
|
|
2114
|
+
const items: SettingItem[] = [
|
|
2115
|
+
{
|
|
2116
|
+
id: "default:future",
|
|
2117
|
+
label: "New badges",
|
|
2118
|
+
description: "Default for badges discovered later. Existing badge choices stay unchanged.",
|
|
2119
|
+
currentValue: statusFilter.mode === "all" ? "shown" : "hidden",
|
|
2120
|
+
values: ["shown", "hidden"],
|
|
2121
|
+
},
|
|
2122
|
+
...knownKeys.map((key): SettingItem => ({
|
|
2123
|
+
id: `badge:${key}`,
|
|
2124
|
+
label: `Badge: ${stripTerminalControls(key)}`,
|
|
2125
|
+
description: statusDescription(key),
|
|
2126
|
+
currentValue: shouldShowStatus(key, statusFilter) ? "shown" : "hidden",
|
|
2127
|
+
values: ["shown", "hidden"],
|
|
2128
|
+
})),
|
|
2129
|
+
];
|
|
2130
|
+
const settings = new SettingsList(items, Math.min(items.length + 2, 15), theme, (id, value) => {
|
|
2131
|
+
if (id === "default:future") {
|
|
2132
|
+
const keys = getKnownStatusKeys(statusFilter, seenStatusKeys);
|
|
2133
|
+
statusFilter = value === "shown"
|
|
2134
|
+
? { mode: "all", hidden: new Set(keys.filter((key) => !shouldShowStatus(key, statusFilter))) }
|
|
2135
|
+
: { mode: "only", shown: new Set(keys.filter((key) => shouldShowStatus(key, statusFilter))) };
|
|
2136
|
+
} else {
|
|
2137
|
+
setBadgeVisibility(id.slice("badge:".length), value === "shown");
|
|
2138
|
+
}
|
|
2139
|
+
persistStatusFilter();
|
|
2140
|
+
}, onClose, { enableSearch: true });
|
|
2141
|
+
return {
|
|
2142
|
+
render(width: number) {
|
|
2143
|
+
return [
|
|
2144
|
+
theme.label("Extension badges", true),
|
|
2145
|
+
theme.hint(knownKeys.length > 0
|
|
2146
|
+
? "Choose badges by key; select a row to preview its text."
|
|
2147
|
+
: "No badges yet. Extensions publish them while you work."),
|
|
2148
|
+
theme.hint("Changes save immediately."),
|
|
2149
|
+
...(!visibleSegments.includes("extensions")
|
|
2150
|
+
? [theme.hint("All badges are hidden. Use /bar show extensions to display them.")]
|
|
2151
|
+
: []),
|
|
2152
|
+
"",
|
|
2153
|
+
...settings.render(Math.max(8, width)),
|
|
2154
|
+
].map((line) => truncateToWidth(line, Math.max(0, width)));
|
|
2155
|
+
},
|
|
2156
|
+
invalidate() { settings.invalidate(); },
|
|
2157
|
+
handleInput(data: string) { settings.handleInput(data); },
|
|
2158
|
+
};
|
|
2159
|
+
};
|
|
2160
|
+
const openSettings = async (ctx: ExtensionContext) => {
|
|
2039
2161
|
if (ctx.mode !== "tui") {
|
|
2040
|
-
ctx.ui.notify("/bar
|
|
2162
|
+
ctx.ui.notify("/bar settings requires TUI mode", "warning");
|
|
2041
2163
|
return;
|
|
2042
2164
|
}
|
|
2043
2165
|
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
2044
|
-
const
|
|
2045
|
-
|
|
2046
|
-
ALL_SEGMENTS.map(
|
|
2047
|
-
(segment): [SegmentName, boolean] => [segment, visibleSegments.includes(segment)],
|
|
2048
|
-
),
|
|
2049
|
-
);
|
|
2050
|
-
let futureShown = statusFilter.mode === "all";
|
|
2051
|
-
const statusVisibility = new Map(
|
|
2052
|
-
knownStatusKeys.map((key): [string, boolean] => [
|
|
2053
|
-
key,
|
|
2054
|
-
shouldShowStatus(key, statusFilter),
|
|
2055
|
-
]),
|
|
2056
|
-
);
|
|
2057
|
-
const persistSegmentsFromVisibility = () => {
|
|
2058
|
-
setVisibleSegments(
|
|
2059
|
-
ALL_SEGMENTS.filter((segment) => segmentVisibility.get(segment)),
|
|
2060
|
-
ctx,
|
|
2061
|
-
);
|
|
2062
|
-
};
|
|
2063
|
-
const persistStatusesFromVisibility = () => {
|
|
2064
|
-
if (futureShown) {
|
|
2065
|
-
statusFilter = {
|
|
2066
|
-
mode: "all",
|
|
2067
|
-
hidden: new Set(
|
|
2068
|
-
knownStatusKeys.filter((key) => !statusVisibility.get(key)),
|
|
2069
|
-
),
|
|
2070
|
-
};
|
|
2071
|
-
} else {
|
|
2072
|
-
statusFilter = {
|
|
2073
|
-
mode: "only",
|
|
2074
|
-
shown: new Set(
|
|
2075
|
-
knownStatusKeys.filter((key) => statusVisibility.get(key)),
|
|
2076
|
-
),
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2079
|
-
persistStatusFilter();
|
|
2080
|
-
};
|
|
2081
|
-
|
|
2082
|
-
const segmentItems: SettingItem[] = ALL_SEGMENTS.flatMap((segment): SettingItem[] => {
|
|
2083
|
-
const visibilityItem: SettingItem = {
|
|
2166
|
+
const items = ALL_SEGMENTS.flatMap((segment): SettingItem[] => {
|
|
2167
|
+
const group: SettingItem[] = [{
|
|
2084
2168
|
id: `segment:${segment}`,
|
|
2085
2169
|
label: SEGMENT_LABELS[segment],
|
|
2086
|
-
description:
|
|
2087
|
-
currentValue:
|
|
2170
|
+
description: SEGMENT_DESCRIPTIONS[segment],
|
|
2171
|
+
currentValue: visibleSegments.includes(segment) ? "shown" : "hidden",
|
|
2088
2172
|
values: ["shown", "hidden"],
|
|
2089
|
-
};
|
|
2090
|
-
if (segment
|
|
2091
|
-
|
|
2173
|
+
}];
|
|
2174
|
+
if (segment === "model") group.push({
|
|
2175
|
+
id: "show-provider",
|
|
2176
|
+
label: "Show provider",
|
|
2177
|
+
description: "Prefix the model name with its provider. Only visible when Model is shown.",
|
|
2178
|
+
currentValue: showProvider ? "shown" : "hidden",
|
|
2179
|
+
values: ["shown", "hidden"],
|
|
2180
|
+
});
|
|
2181
|
+
if (segment === "progress") group.push({
|
|
2092
2182
|
id: "progress-model",
|
|
2093
2183
|
label: "Progress model",
|
|
2094
2184
|
description: process.env.PI_BAR_PROGRESS_MODEL
|
|
2095
2185
|
? "Controlled by PI_BAR_PROGRESS_MODEL (read-only). Unset it and restart Pi to choose here."
|
|
2096
|
-
: "Choose Auto or search models with configured Pi credentials.
|
|
2186
|
+
: "Choose Auto or search models with configured Pi credentials. Changes only progress updates, not the chat model.",
|
|
2097
2187
|
currentValue: formatProgressModelKey(resolveProgressModelPreference(ctx.cwd)),
|
|
2098
2188
|
submenu: process.env.PI_BAR_PROGRESS_MODEL
|
|
2099
2189
|
? undefined
|
|
2100
2190
|
: (currentValue, close) => createProgressModelPicker(ctx, currentValue, close),
|
|
2101
|
-
}
|
|
2191
|
+
});
|
|
2192
|
+
if (segment === "extensions") group.push({
|
|
2193
|
+
id: "badge-settings",
|
|
2194
|
+
label: "Choose badges",
|
|
2195
|
+
description: "Choose individual badges from other extensions. They display only when Extension badges is shown.",
|
|
2196
|
+
currentValue: badgeSummary(),
|
|
2197
|
+
submenu: (_currentValue, close) => createBadgeSettings(() => close(badgeSummary()), "go back"),
|
|
2198
|
+
});
|
|
2199
|
+
return group;
|
|
2102
2200
|
});
|
|
2103
|
-
const statusItems: SettingItem[] = knownStatusKeys.length > 0
|
|
2104
|
-
? [
|
|
2105
|
-
{
|
|
2106
|
-
id: "status:__future",
|
|
2107
|
-
label: "New extension statuses",
|
|
2108
|
-
description: "Default visibility for status keys discovered later",
|
|
2109
|
-
currentValue: futureShown ? "shown" : "hidden",
|
|
2110
|
-
values: ["shown", "hidden"],
|
|
2111
|
-
},
|
|
2112
|
-
...knownStatusKeys.map((key): SettingItem => ({
|
|
2113
|
-
id: `status:${key}`,
|
|
2114
|
-
label: `Status: ${stripTerminalControls(key)}`,
|
|
2115
|
-
description: statusDescription(key),
|
|
2116
|
-
currentValue: statusVisibility.get(key) ? "shown" : "hidden",
|
|
2117
|
-
values: ["shown", "hidden"],
|
|
2118
|
-
})),
|
|
2119
|
-
]
|
|
2120
|
-
: [];
|
|
2121
|
-
const items: SettingItem[] = [
|
|
2122
|
-
...segmentItems,
|
|
2123
|
-
{
|
|
2124
|
-
id: "show-provider",
|
|
2125
|
-
label: "Show provider",
|
|
2126
|
-
description: "Provider prefix inside the model segment (hidden by default)",
|
|
2127
|
-
currentValue: showProvider ? "shown" : "hidden",
|
|
2128
|
-
values: ["shown", "hidden"],
|
|
2129
|
-
},
|
|
2130
|
-
...statusItems,
|
|
2131
|
-
];
|
|
2132
|
-
|
|
2133
2201
|
const container = new Container();
|
|
2134
|
-
container.addChild(
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
settingsList.updateValue(id, formatProgressModelKey(resolveProgressModelPreference(ctx.cwd)));
|
|
2160
|
-
return;
|
|
2161
|
-
}
|
|
2162
|
-
if (id === "show-provider") {
|
|
2163
|
-
setShowProvider(newValue === "shown");
|
|
2164
|
-
return;
|
|
2165
|
-
}
|
|
2166
|
-
if (id.startsWith("segment:")) {
|
|
2167
|
-
const segment = id.slice("segment:".length);
|
|
2168
|
-
if (!isSegmentName(segment)) return;
|
|
2169
|
-
segmentVisibility.set(segment, newValue === "shown");
|
|
2170
|
-
persistSegmentsFromVisibility();
|
|
2171
|
-
return;
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
if (id === "status:__future") {
|
|
2175
|
-
futureShown = newValue === "shown";
|
|
2176
|
-
persistStatusesFromVisibility();
|
|
2177
|
-
return;
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2180
|
-
if (id.startsWith("status:")) {
|
|
2181
|
-
statusVisibility.set(id.slice("status:".length), newValue === "shown");
|
|
2182
|
-
persistStatusesFromVisibility();
|
|
2183
|
-
}
|
|
2184
|
-
},
|
|
2185
|
-
() => done(undefined),
|
|
2186
|
-
{ enableSearch: true },
|
|
2187
|
-
);
|
|
2188
|
-
|
|
2189
|
-
container.addChild(settingsList);
|
|
2190
|
-
|
|
2202
|
+
container.addChild(new (class {
|
|
2203
|
+
render(_width: number) {
|
|
2204
|
+
return [
|
|
2205
|
+
theme.fg("accent", theme.bold("pi-bar settings")),
|
|
2206
|
+
theme.fg("dim", "Choose what appears in your footer. Changes save immediately."),
|
|
2207
|
+
"",
|
|
2208
|
+
];
|
|
2209
|
+
}
|
|
2210
|
+
invalidate() {}
|
|
2211
|
+
})());
|
|
2212
|
+
const settings = new SettingsList(items, Math.min(items.length + 2, 18), savedSettingsTheme("close"), (id, value) => {
|
|
2213
|
+
if (id === "progress-model") {
|
|
2214
|
+
setProgressModel(value, ctx);
|
|
2215
|
+
settings.updateValue(id, formatProgressModelKey(resolveProgressModelPreference(ctx.cwd)));
|
|
2216
|
+
} else if (id === "show-provider") {
|
|
2217
|
+
setShowProvider(value === "shown");
|
|
2218
|
+
} else if (id.startsWith("segment:")) {
|
|
2219
|
+
const segment = id.slice("segment:".length);
|
|
2220
|
+
if (!isSegmentName(segment)) return;
|
|
2221
|
+
setVisibleSegments(value === "shown"
|
|
2222
|
+
? [...visibleSegments, segment]
|
|
2223
|
+
: visibleSegments.filter((item) => item !== segment), ctx);
|
|
2224
|
+
}
|
|
2225
|
+
}, () => done(undefined), { enableSearch: true });
|
|
2226
|
+
container.addChild(settings);
|
|
2191
2227
|
return {
|
|
2192
2228
|
render(width: number) {
|
|
2193
2229
|
return container.render(Math.max(8, width)).map((line) => truncateToWidth(line, Math.max(0, width)));
|
|
2194
2230
|
},
|
|
2195
|
-
invalidate() {
|
|
2196
|
-
container.invalidate();
|
|
2197
|
-
},
|
|
2231
|
+
invalidate() { container.invalidate(); },
|
|
2198
2232
|
handleInput(data: string) {
|
|
2199
|
-
|
|
2233
|
+
settings.handleInput(data);
|
|
2200
2234
|
tui.requestRender();
|
|
2201
2235
|
},
|
|
2202
2236
|
};
|
|
2203
2237
|
});
|
|
2204
2238
|
};
|
|
2205
|
-
const
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
ctx.ui.notify(
|
|
2209
|
-
"No extension statuses seen yet. Open /bar after another extension calls ctx.ui.setStatus().",
|
|
2210
|
-
"info",
|
|
2211
|
-
);
|
|
2239
|
+
const openBadges = async (ctx: ExtensionContext) => {
|
|
2240
|
+
if (ctx.mode !== "tui") {
|
|
2241
|
+
ctx.ui.notify("/bar badges requires TUI mode", "warning");
|
|
2212
2242
|
return;
|
|
2213
2243
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
let futureShown = statusFilter.mode === "all";
|
|
2217
|
-
const statusVisibility = new Map(
|
|
2218
|
-
knownStatusKeys.map((key) => [key, shouldShowStatus(key, statusFilter)]),
|
|
2219
|
-
);
|
|
2220
|
-
const persistFromVisibility = () => {
|
|
2221
|
-
if (futureShown) {
|
|
2222
|
-
statusFilter = {
|
|
2223
|
-
mode: "all",
|
|
2224
|
-
hidden: new Set(
|
|
2225
|
-
knownStatusKeys.filter((key) => !statusVisibility.get(key)),
|
|
2226
|
-
),
|
|
2227
|
-
};
|
|
2228
|
-
} else {
|
|
2229
|
-
statusFilter = {
|
|
2230
|
-
mode: "only",
|
|
2231
|
-
shown: new Set(
|
|
2232
|
-
knownStatusKeys.filter((key) => statusVisibility.get(key)),
|
|
2233
|
-
),
|
|
2234
|
-
};
|
|
2235
|
-
}
|
|
2236
|
-
persistStatusFilter();
|
|
2237
|
-
};
|
|
2238
|
-
|
|
2239
|
-
const items: SettingItem[] = [
|
|
2240
|
-
{
|
|
2241
|
-
id: "__future",
|
|
2242
|
-
label: "New statuses",
|
|
2243
|
-
description: "Default visibility for status keys discovered later",
|
|
2244
|
-
currentValue: futureShown ? "shown" : "hidden",
|
|
2245
|
-
values: ["shown", "hidden"],
|
|
2246
|
-
},
|
|
2247
|
-
...knownStatusKeys.map((key): SettingItem => ({
|
|
2248
|
-
id: key,
|
|
2249
|
-
label: stripTerminalControls(key),
|
|
2250
|
-
description: statusDescription(key),
|
|
2251
|
-
currentValue: statusVisibility.get(key) ? "shown" : "hidden",
|
|
2252
|
-
values: ["shown", "hidden"],
|
|
2253
|
-
})),
|
|
2254
|
-
];
|
|
2255
|
-
|
|
2256
|
-
const container = new Container();
|
|
2257
|
-
container.addChild(
|
|
2258
|
-
new (class {
|
|
2259
|
-
render(_width: number) {
|
|
2260
|
-
return [
|
|
2261
|
-
theme.fg("accent", theme.bold("pi-bar status visibility")),
|
|
2262
|
-
theme.fg("dim", "Enter/Space toggles · Esc closes"),
|
|
2263
|
-
"",
|
|
2264
|
-
];
|
|
2265
|
-
}
|
|
2266
|
-
invalidate() {}
|
|
2267
|
-
})(),
|
|
2268
|
-
);
|
|
2269
|
-
|
|
2270
|
-
const settingsList = new SettingsList(
|
|
2271
|
-
items,
|
|
2272
|
-
Math.min(items.length + 2, 15),
|
|
2273
|
-
getSettingsListTheme(),
|
|
2274
|
-
(id, newValue) => {
|
|
2275
|
-
if (id === "__future") {
|
|
2276
|
-
futureShown = newValue === "shown";
|
|
2277
|
-
} else {
|
|
2278
|
-
statusVisibility.set(id, newValue === "shown");
|
|
2279
|
-
}
|
|
2280
|
-
persistFromVisibility();
|
|
2281
|
-
},
|
|
2282
|
-
() => done(undefined),
|
|
2283
|
-
{ enableSearch: true },
|
|
2284
|
-
);
|
|
2285
|
-
|
|
2286
|
-
container.addChild(settingsList);
|
|
2287
|
-
|
|
2244
|
+
await ctx.ui.custom((tui, _theme, _kb, done) => {
|
|
2245
|
+
const settings = createBadgeSettings(() => done(undefined), "close");
|
|
2288
2246
|
return {
|
|
2289
|
-
|
|
2290
|
-
return container.render(width);
|
|
2291
|
-
},
|
|
2292
|
-
invalidate() {
|
|
2293
|
-
container.invalidate();
|
|
2294
|
-
},
|
|
2247
|
+
...settings,
|
|
2295
2248
|
handleInput(data: string) {
|
|
2296
|
-
|
|
2249
|
+
settings.handleInput(data);
|
|
2297
2250
|
tui.requestRender();
|
|
2298
2251
|
},
|
|
2299
2252
|
};
|
|
@@ -2301,7 +2254,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2301
2254
|
};
|
|
2302
2255
|
|
|
2303
2256
|
pi.registerCommand("bar", {
|
|
2304
|
-
description: "
|
|
2257
|
+
description: "Open footer settings; choose what appears in the bar",
|
|
2305
2258
|
getArgumentCompletions: (prefix) => {
|
|
2306
2259
|
let modelKeys: string[] = [];
|
|
2307
2260
|
if (/^\s*progress-model\s+\S*$/.test(prefix)) {
|
|
@@ -2313,15 +2266,55 @@ export default function (pi: ExtensionAPI) {
|
|
|
2313
2266
|
return completeBarArguments(prefix, getKnownStatusKeys(statusFilter, seenStatusKeys), modelKeys);
|
|
2314
2267
|
},
|
|
2315
2268
|
handler: async (args, ctx) => {
|
|
2316
|
-
const
|
|
2317
|
-
if (
|
|
2318
|
-
|
|
2269
|
+
const words = args.trim().split(/\s+/).filter(Boolean);
|
|
2270
|
+
if (words[0] === "show" || words[0] === "hide") {
|
|
2271
|
+
const names = splitStatusKeys(words.slice(1).join(" ")).map((name) => name.toLowerCase());
|
|
2272
|
+
if (names.includes("all")) {
|
|
2273
|
+
if (names.length !== 1) {
|
|
2274
|
+
ctx.ui.notify("Use all by itself: /bar show all or /bar hide all.", "warning");
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
words.splice(0, words.length, "segments", words[0] === "show" ? "all" : "none");
|
|
2278
|
+
} else {
|
|
2279
|
+
const unknown = names.filter((name) => !isSegmentName(name));
|
|
2280
|
+
if (unknown.length > 0) {
|
|
2281
|
+
ctx.ui.notify(`Unknown footer items: ${unknown.map(stripTerminalControls).join(", ")}. Use Tab to choose items.`, "warning");
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
words.unshift("segments");
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
if (words[0] === "badges") {
|
|
2288
|
+
if (["show", "hide", "only"].includes(words[1]) && splitStatusKeys(words.slice(2).join(" ")).length === 0) {
|
|
2289
|
+
ctx.ui.notify("Choose badge keys with Tab, or open /bar badges to choose them interactively.", "warning");
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
words[0] = "status";
|
|
2293
|
+
}
|
|
2294
|
+
const [section, action, ...rest] = words;
|
|
2295
|
+
if (!section || section === "settings" || section === "config" || section === "configure" || section === "edit") {
|
|
2296
|
+
await openSettings(ctx);
|
|
2319
2297
|
return;
|
|
2320
2298
|
}
|
|
2321
2299
|
if (section === "list" || section === "ls") {
|
|
2322
2300
|
ctx.ui.notify(`pi-bar footer: ${describeSegments(visibleSegments)}`, "info");
|
|
2323
2301
|
return;
|
|
2324
2302
|
}
|
|
2303
|
+
if (section === "help") {
|
|
2304
|
+
ctx.ui.notify([
|
|
2305
|
+
"Open /bar for all footer settings. Changes save immediately.",
|
|
2306
|
+
"",
|
|
2307
|
+
...Array.from(BAR_COMMAND_DESCRIPTIONS, ([name, description]) => `/bar ${name} — ${description}`),
|
|
2308
|
+
"",
|
|
2309
|
+
"Examples: /bar show cost tokens · /bar hide progress · /bar badges hide mcp",
|
|
2310
|
+
"Use /bar show all or /bar hide all to change every footer item.",
|
|
2311
|
+
`Footer items: ${ALL_SEGMENTS.join(", ")}`,
|
|
2312
|
+
`Current configuration: ${describeSegments(visibleSegments)}`,
|
|
2313
|
+
"Enabled items may wait for data or yield under width pressure.",
|
|
2314
|
+
"Old commands still work: /bar config, /bar segments, /bar status, /bar list.",
|
|
2315
|
+
].join("\n"), "info");
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2325
2318
|
|
|
2326
2319
|
if (section === "progress-model") {
|
|
2327
2320
|
if (!action) {
|
|
@@ -2359,7 +2352,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2359
2352
|
const segments = splitSegmentNames(rest.join(" "));
|
|
2360
2353
|
if ((action === "only" || action === "show" || action === "hide") && segments.length === 0) {
|
|
2361
2354
|
ctx.ui.notify(
|
|
2362
|
-
`
|
|
2355
|
+
`Footer items: ${ALL_SEGMENTS.join(", ")}. Use Tab to choose.`,
|
|
2363
2356
|
"warning",
|
|
2364
2357
|
);
|
|
2365
2358
|
return;
|
|
@@ -2367,10 +2360,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
2367
2360
|
|
|
2368
2361
|
switch (action) {
|
|
2369
2362
|
case undefined:
|
|
2363
|
+
case "settings":
|
|
2370
2364
|
case "config":
|
|
2371
2365
|
case "configure":
|
|
2372
2366
|
case "edit":
|
|
2373
|
-
await
|
|
2367
|
+
await openSettings(ctx);
|
|
2374
2368
|
return;
|
|
2375
2369
|
case "list":
|
|
2376
2370
|
case "ls":
|
|
@@ -2396,7 +2390,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2396
2390
|
break;
|
|
2397
2391
|
default:
|
|
2398
2392
|
ctx.ui.notify(
|
|
2399
|
-
"
|
|
2393
|
+
"Use /bar show <items> or /bar hide <items>. Open /bar for all settings.",
|
|
2400
2394
|
"warning",
|
|
2401
2395
|
);
|
|
2402
2396
|
return;
|
|
@@ -2407,7 +2401,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2407
2401
|
}
|
|
2408
2402
|
|
|
2409
2403
|
if ((section === "status" || section === "statuses") && !action) {
|
|
2410
|
-
await
|
|
2404
|
+
await openBadges(ctx);
|
|
2411
2405
|
return;
|
|
2412
2406
|
}
|
|
2413
2407
|
|
|
@@ -2415,16 +2409,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
2415
2409
|
const keys = splitStatusKeys(rest.join(" "));
|
|
2416
2410
|
|
|
2417
2411
|
switch (action) {
|
|
2412
|
+
case "settings":
|
|
2418
2413
|
case "config":
|
|
2419
2414
|
case "configure":
|
|
2420
2415
|
case "edit":
|
|
2421
|
-
await
|
|
2416
|
+
await openBadges(ctx);
|
|
2422
2417
|
return;
|
|
2423
2418
|
case "list":
|
|
2424
2419
|
case "ls": {
|
|
2425
2420
|
const known = getKnownStatusKeys(statusFilter, seenStatusKeys);
|
|
2426
2421
|
ctx.ui.notify(
|
|
2427
|
-
`pi-bar
|
|
2422
|
+
`pi-bar badges: ${describeStatusFilter(statusFilter)}${known.length > 0 ? `; known: ${known.map(stripTerminalControls).join(", ")}` : "; known: none yet"}`,
|
|
2428
2423
|
"info",
|
|
2429
2424
|
);
|
|
2430
2425
|
return;
|
|
@@ -2439,34 +2434,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
2439
2434
|
statusFilter = { mode: "only", shown: new Set(keys) };
|
|
2440
2435
|
break;
|
|
2441
2436
|
case "hide":
|
|
2442
|
-
if (statusFilter.mode === "only") {
|
|
2443
|
-
for (const key of keys) statusFilter.shown.delete(key);
|
|
2444
|
-
} else {
|
|
2445
|
-
for (const key of keys) statusFilter.hidden.add(key);
|
|
2446
|
-
}
|
|
2447
|
-
break;
|
|
2448
2437
|
case "show":
|
|
2449
|
-
|
|
2450
|
-
for (const key of keys) statusFilter.shown.add(key);
|
|
2451
|
-
} else {
|
|
2452
|
-
for (const key of keys) statusFilter.hidden.delete(key);
|
|
2453
|
-
}
|
|
2438
|
+
for (const key of keys) setBadgeVisibility(key, action === "show");
|
|
2454
2439
|
break;
|
|
2455
2440
|
default:
|
|
2456
2441
|
ctx.ui.notify(
|
|
2457
|
-
"
|
|
2442
|
+
"Open /bar badges to choose badges, or use /bar badges show|hide <keys>.",
|
|
2458
2443
|
"warning",
|
|
2459
2444
|
);
|
|
2460
2445
|
return;
|
|
2461
2446
|
}
|
|
2462
2447
|
|
|
2463
2448
|
persistStatusFilter();
|
|
2464
|
-
ctx.ui.notify(`pi-bar
|
|
2449
|
+
ctx.ui.notify(`pi-bar badges: ${describeStatusFilter(statusFilter)}`, "info");
|
|
2465
2450
|
return;
|
|
2466
2451
|
}
|
|
2467
2452
|
|
|
2468
2453
|
ctx.ui.notify(
|
|
2469
|
-
"
|
|
2454
|
+
"Unknown /bar command. Open /bar for settings or /bar help for examples.",
|
|
2470
2455
|
"warning",
|
|
2471
2456
|
);
|
|
2472
2457
|
},
|
|
@@ -2580,7 +2565,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2580
2565
|
alternatives: [theme.fg(contextSegmentColor, contextCompact)],
|
|
2581
2566
|
},
|
|
2582
2567
|
cache_hit_ratio: {
|
|
2583
|
-
text: metrics.cacheHitRate === null ? "" : theme.fg(
|
|
2568
|
+
text: metrics.cacheHitRate === null ? "" : theme.fg(cacheHitColor(metrics.cacheHitRate), `CH:${metrics.cacheHitRate.toFixed(1).replace(/\.0$/, "")}%`),
|
|
2584
2569
|
},
|
|
2585
2570
|
cost: { text: reportedUsage ? theme.fg("muted", `≈$${metrics.cost.toFixed(3)}`) : "" },
|
|
2586
2571
|
tokens: { text: reportedUsage ? theme.fg("muted", `↑${formatTokens(metrics.input)} ↓${formatTokens(metrics.output)}`) : "" },
|
package/package.json
CHANGED