@narumitw/pi-statusline 0.35.0 → 0.37.0
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 +26 -9
- package/package.json +1 -1
- package/src/commands.ts +1 -0
- package/src/information-profiles.ts +2 -1
- package/src/powerline.ts +1 -0
- package/src/presets/index.ts +1 -0
- package/src/render.ts +34 -34
- package/src/settings.ts +1 -0
- package/src/types.ts +1 -0
- package/src/usage.ts +53 -0
package/README.md
CHANGED
|
@@ -8,14 +8,15 @@ without setup and keeps useful context visible as the terminal narrows.
|
|
|
8
8
|
A representative uncolored layout:
|
|
9
9
|
|
|
10
10
|
```text
|
|
11
|
-
░▒▓ 🤖 sonnet-4 🧠 high 📁 pi-extensions 🌿 main ~2 🪟 ctx 42
|
|
11
|
+
░▒▓ 🤖 sonnet-4 🧠 high 📁 pi-extensions 🌿 main ~2 🪟 ctx 42.0%/200k 🕒 16:42
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## ✨ Features
|
|
15
15
|
|
|
16
|
-
- **Zero-config default:** model, thinking, workspace, Git/PR state, context use, and
|
|
16
|
+
- **Zero-config default:** model, thinking, workspace, Git/PR state, context use, and local time.
|
|
17
17
|
- **Responsive:** removes lower-priority segments before important information gets clipped.
|
|
18
18
|
- **Quiet when idle:** activity appears only while Pi is streaming or running tools.
|
|
19
|
+
- **Native-aligned usage:** optional token, prompt-cache, subscription, and cost details.
|
|
19
20
|
- **Easy choices:** three information levels and seven previewable color presets.
|
|
20
21
|
- **Still flexible:** custom layouts, multiline rows, colors, labels, separators, and status icons stay
|
|
21
22
|
available under **Advanced**.
|
|
@@ -73,11 +74,12 @@ preserved.
|
|
|
73
74
|
| Level | Included segments |
|
|
74
75
|
| --- | --- |
|
|
75
76
|
| **Minimal** | `model cwd branch context` |
|
|
76
|
-
| **Balanced** (default) | `model thinking cwd branch tools context
|
|
77
|
-
| **Detailed** | `provider model thinking cwd branch tools context tokens cost time` |
|
|
77
|
+
| **Balanced** (default) | `model thinking cwd branch tools context time` |
|
|
78
|
+
| **Detailed** | `provider model thinking cwd branch tools context tokens cache cost time` |
|
|
78
79
|
| **Custom** | Any other segment order, including explicit line breaks |
|
|
79
80
|
|
|
80
|
-
The `tools` segment takes no space while idle.
|
|
81
|
+
The `tools` segment takes no space while idle. `cache` takes no space when Pi has reported no cache
|
|
82
|
+
reads or writes.
|
|
81
83
|
|
|
82
84
|
## 💬 Commands
|
|
83
85
|
|
|
@@ -100,7 +102,7 @@ lowest-priority segment, recomputes the powerline transitions, and repeats until
|
|
|
100
102
|
Retention priority is highest to lowest:
|
|
101
103
|
|
|
102
104
|
```text
|
|
103
|
-
context model branch tools cwd thinking cost provider tokens time turn brand
|
|
105
|
+
context model branch tools cwd thinking cost provider cache tokens time turn brand
|
|
104
106
|
```
|
|
105
107
|
|
|
106
108
|
Explicit `line_break` entries remain row boundaries. A single segment that is still too wide is
|
|
@@ -117,6 +119,20 @@ ANSI-safely truncated.
|
|
|
117
119
|
- Context color changes to warning at 70% and error at 90%.
|
|
118
120
|
- Git state is cached outside footer rendering and stale session results are ignored.
|
|
119
121
|
|
|
122
|
+
### Usage and context
|
|
123
|
+
|
|
124
|
+
- `context` renders one-decimal current usage and the model window, such as `2.4%/272k`. After
|
|
125
|
+
compaction it can temporarily render `?/272k` until the next valid assistant response.
|
|
126
|
+
- `tokens`, `cache`, and `cost` total every usage-bearing session entry, matching Pi's native footer:
|
|
127
|
+
assistant messages, nested-LLM tool results, compactions, and branch summaries, including abandoned
|
|
128
|
+
branches retained in the session.
|
|
129
|
+
- Cache tokens are `R<read>`, `W<write>`, and `CH<rate>`. `R` and `W` are cumulative; `CH` uses only
|
|
130
|
+
the latest assistant prompt: `cacheRead / (input + cacheRead + cacheWrite) * 100`.
|
|
131
|
+
- Subscription-backed OAuth models and `kimi-coding` append `(sub)` to cost. The dollar value is
|
|
132
|
+
usage cost, not proof of an amount billed under a subscription.
|
|
133
|
+
- Pi's public extension API does not expose the current auto-compaction toggle, so this footer cannot
|
|
134
|
+
reliably show the native `(auto)` marker.
|
|
135
|
+
|
|
120
136
|
## ⚙️ Settings
|
|
121
137
|
|
|
122
138
|
The extension uses one user-level file:
|
|
@@ -156,7 +172,7 @@ A compact customization example:
|
|
|
156
172
|
"palettePreset": "ocean",
|
|
157
173
|
"density": "compact",
|
|
158
174
|
"separator": "dot",
|
|
159
|
-
"segments": ["model", "thinking", "cwd", "branch", "context", "cost"],
|
|
175
|
+
"segments": ["model", "thinking", "cwd", "branch", "context", "cache", "cost"],
|
|
160
176
|
"segmentText": {
|
|
161
177
|
"context": { "prefix": "ctx ", "suffix": "" }
|
|
162
178
|
},
|
|
@@ -218,7 +234,7 @@ Every successful change saves and applies immediately; closing the screen does n
|
|
|
218
234
|
Available data segments:
|
|
219
235
|
|
|
220
236
|
```text
|
|
221
|
-
brand provider model thinking cwd branch tools context tokens cost time turn
|
|
237
|
+
brand provider model thinking cwd branch tools context tokens cache cost time turn
|
|
222
238
|
```
|
|
223
239
|
|
|
224
240
|
Data segments must be unique. `line_break` may repeat when data segments separate occurrences, but
|
|
@@ -278,6 +294,7 @@ extensions/pi-statusline/
|
|
|
278
294
|
│ ├── index.ts
|
|
279
295
|
│ ├── statusline.ts
|
|
280
296
|
│ ├── render.ts
|
|
297
|
+
│ ├── usage.ts
|
|
281
298
|
│ ├── powerline.ts
|
|
282
299
|
│ ├── information-profiles.ts
|
|
283
300
|
│ ├── commands.ts
|
|
@@ -299,7 +316,7 @@ extensions/pi-statusline/
|
|
|
299
316
|
## 🔎 Keywords
|
|
300
317
|
|
|
301
318
|
Pi extension, Pi coding agent, statusline, Tokyo Night, powerline, responsive terminal footer,
|
|
302
|
-
context usage, model status.
|
|
319
|
+
context usage, prompt cache, cache hit rate, model status.
|
|
303
320
|
|
|
304
321
|
## 📄 License
|
|
305
322
|
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -51,6 +51,7 @@ const SEGMENT_DESCRIPTIONS: Record<SegmentName, string> = {
|
|
|
51
51
|
tools: "Current tool and streaming activity",
|
|
52
52
|
context: "Current context-window usage",
|
|
53
53
|
tokens: "Session token totals",
|
|
54
|
+
cache: "Prompt-cache reads, writes, and latest hit rate",
|
|
54
55
|
cost: "Session cost",
|
|
55
56
|
time: "Current local time",
|
|
56
57
|
turn: "Current session turn count",
|
|
@@ -8,7 +8,7 @@ export const INFORMATION_PROFILES: Readonly<
|
|
|
8
8
|
Record<InformationProfileName, readonly SegmentName[]>
|
|
9
9
|
> = {
|
|
10
10
|
minimal: ["model", "cwd", "branch", "context"],
|
|
11
|
-
balanced: ["model", "thinking", "cwd", "branch", "tools", "context", "
|
|
11
|
+
balanced: ["model", "thinking", "cwd", "branch", "tools", "context", "time"],
|
|
12
12
|
detailed: [
|
|
13
13
|
"provider",
|
|
14
14
|
"model",
|
|
@@ -18,6 +18,7 @@ export const INFORMATION_PROFILES: Readonly<
|
|
|
18
18
|
"tools",
|
|
19
19
|
"context",
|
|
20
20
|
"tokens",
|
|
21
|
+
"cache",
|
|
21
22
|
"cost",
|
|
22
23
|
"time",
|
|
23
24
|
],
|
package/src/powerline.ts
CHANGED
package/src/presets/index.ts
CHANGED
package/src/render.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type SegmentName,
|
|
22
22
|
type StatuslineConfig,
|
|
23
23
|
} from "./types.js";
|
|
24
|
+
import { type FooterUsageSummary, summarizeFooterUsage } from "./usage.js";
|
|
24
25
|
|
|
25
26
|
type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
26
27
|
export interface RuntimeState extends ExtensionStatusRuntime {
|
|
@@ -31,11 +32,6 @@ export interface RuntimeState extends ExtensionStatusRuntime {
|
|
|
31
32
|
gitStatus?: GitStatusSummary;
|
|
32
33
|
requestRender?: () => void;
|
|
33
34
|
}
|
|
34
|
-
interface TokenTotals {
|
|
35
|
-
input: number;
|
|
36
|
-
output: number;
|
|
37
|
-
cost: number;
|
|
38
|
-
}
|
|
39
35
|
const GITHUB_PR_KEY = "github-pr";
|
|
40
36
|
const GITHUB_PR_STATUS_KEYS = new Set([GITHUB_PR_KEY]);
|
|
41
37
|
export function renderStatusline(
|
|
@@ -48,6 +44,7 @@ export function renderStatusline(
|
|
|
48
44
|
): string {
|
|
49
45
|
if (width <= 0) return "";
|
|
50
46
|
|
|
47
|
+
const usageSummary = summarizeFooterUsage(ctx.sessionManager.getEntries());
|
|
51
48
|
const rows: Array<{ configuredSegments: number; segments: RenderSegment[] }> = [
|
|
52
49
|
{ configuredSegments: 0, segments: [] },
|
|
53
50
|
];
|
|
@@ -60,7 +57,7 @@ export function renderStatusline(
|
|
|
60
57
|
const row = rows.at(-1);
|
|
61
58
|
if (!row) continue;
|
|
62
59
|
row.configuredSegments += 1;
|
|
63
|
-
const rendered = buildSegment(name, ctx, footerData, config, runtime);
|
|
60
|
+
const rendered = buildSegment(name, ctx, footerData, config, runtime, usageSummary);
|
|
64
61
|
if (rendered && rendered.text.length > 0) row.segments.push(rendered);
|
|
65
62
|
}
|
|
66
63
|
|
|
@@ -103,6 +100,7 @@ function buildSegment(
|
|
|
103
100
|
footerData: ReadonlyFooterDataProvider,
|
|
104
101
|
config: StatuslineConfig,
|
|
105
102
|
runtime: RuntimeState,
|
|
103
|
+
usageSummary: FooterUsageSummary,
|
|
106
104
|
): RenderSegment | undefined {
|
|
107
105
|
switch (name) {
|
|
108
106
|
case "brand":
|
|
@@ -138,25 +136,41 @@ function buildSegment(
|
|
|
138
136
|
}
|
|
139
137
|
case "context": {
|
|
140
138
|
const usage = ctx.getContextUsage();
|
|
141
|
-
const
|
|
139
|
+
const percentage =
|
|
142
140
|
usage?.percent === null || usage?.percent === undefined
|
|
143
141
|
? "?"
|
|
144
|
-
: `${usage.percent.toFixed(
|
|
145
|
-
|
|
142
|
+
: `${usage.percent.toFixed(1)}%`;
|
|
143
|
+
const contextWindow = usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
144
|
+
return segment(
|
|
145
|
+
name,
|
|
146
|
+
`${percentage}/${formatCount(contextWindow)}`,
|
|
147
|
+
config,
|
|
148
|
+
contextColor(usage?.percent),
|
|
149
|
+
"runtime",
|
|
150
|
+
);
|
|
146
151
|
}
|
|
147
152
|
case "tokens": {
|
|
148
|
-
const totals = getTokenTotals(ctx);
|
|
149
153
|
const value =
|
|
150
|
-
|
|
154
|
+
usageSummary.input === 0 && usageSummary.output === 0
|
|
151
155
|
? "tok 0"
|
|
152
|
-
: `↑${formatCount(
|
|
156
|
+
: `↑${formatCount(usageSummary.input)} ↓${formatCount(usageSummary.output)}`;
|
|
153
157
|
return segment(name, value, config, "accent", "runtime");
|
|
154
158
|
}
|
|
159
|
+
case "cache": {
|
|
160
|
+
if (usageSummary.cacheRead === 0 && usageSummary.cacheWrite === 0) return undefined;
|
|
161
|
+
const values: string[] = [];
|
|
162
|
+
if (usageSummary.cacheRead > 0) values.push(`R${formatCount(usageSummary.cacheRead)}`);
|
|
163
|
+
if (usageSummary.cacheWrite > 0) values.push(`W${formatCount(usageSummary.cacheWrite)}`);
|
|
164
|
+
if (usageSummary.latestCacheHitRate !== undefined) {
|
|
165
|
+
values.push(`CH${usageSummary.latestCacheHitRate.toFixed(1)}%`);
|
|
166
|
+
}
|
|
167
|
+
return segment(name, values.join(" "), config, "accent", "runtime");
|
|
168
|
+
}
|
|
155
169
|
case "cost": {
|
|
156
|
-
const
|
|
170
|
+
const subscription = isSubscriptionBacked(ctx) ? " (sub)" : "";
|
|
157
171
|
return segment(
|
|
158
172
|
name,
|
|
159
|
-
|
|
173
|
+
`${usageSummary.cost.toFixed(usageSummary.cost >= 1 ? 2 : 3)}${subscription}`,
|
|
160
174
|
config,
|
|
161
175
|
"accent",
|
|
162
176
|
"meter",
|
|
@@ -268,26 +282,12 @@ function compactPrState(value: string): string | undefined {
|
|
|
268
282
|
return undefined;
|
|
269
283
|
}
|
|
270
284
|
|
|
271
|
-
function
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const usage = entry.message.usage as
|
|
278
|
-
| {
|
|
279
|
-
input?: number;
|
|
280
|
-
output?: number;
|
|
281
|
-
cost?: { total?: number };
|
|
282
|
-
}
|
|
283
|
-
| undefined;
|
|
284
|
-
|
|
285
|
-
totals.input += usage?.input ?? 0;
|
|
286
|
-
totals.output += usage?.output ?? 0;
|
|
287
|
-
totals.cost += usage?.cost?.total ?? 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return totals;
|
|
285
|
+
function isSubscriptionBacked(ctx: ExtensionContext): boolean {
|
|
286
|
+
const model = ctx.model;
|
|
287
|
+
return (
|
|
288
|
+
model !== undefined &&
|
|
289
|
+
(model.provider === "kimi-coding" || ctx.modelRegistry.isUsingOAuth(model))
|
|
290
|
+
);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
export function formatCount(value: number): string {
|
package/src/settings.ts
CHANGED
|
@@ -67,6 +67,7 @@ export const DEFAULT_STATUSLINE_CONFIG: StatuslineConfig = {
|
|
|
67
67
|
tools: { prefix: "", suffix: "" },
|
|
68
68
|
context: { prefix: "🪟 ctx ", suffix: "" },
|
|
69
69
|
tokens: { prefix: "🔢 ", suffix: "" },
|
|
70
|
+
cache: { prefix: "📦 ", suffix: "" },
|
|
70
71
|
cost: { prefix: "💸 $", suffix: "" },
|
|
71
72
|
time: { prefix: "🕒 ", suffix: "" },
|
|
72
73
|
turn: { prefix: "🔁 #", suffix: "" },
|
package/src/types.ts
CHANGED
package/src/usage.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
interface UsageLike {
|
|
4
|
+
input?: number;
|
|
5
|
+
output?: number;
|
|
6
|
+
cacheRead?: number;
|
|
7
|
+
cacheWrite?: number;
|
|
8
|
+
cost?: { total?: number };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FooterUsageSummary {
|
|
12
|
+
input: number;
|
|
13
|
+
output: number;
|
|
14
|
+
cacheRead: number;
|
|
15
|
+
cacheWrite: number;
|
|
16
|
+
cost: number;
|
|
17
|
+
latestCacheHitRate?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function summarizeFooterUsage(entries: readonly SessionEntry[]): FooterUsageSummary {
|
|
21
|
+
const totals: FooterUsageSummary = {
|
|
22
|
+
input: 0,
|
|
23
|
+
output: 0,
|
|
24
|
+
cacheRead: 0,
|
|
25
|
+
cacheWrite: 0,
|
|
26
|
+
cost: 0,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
let usage: UsageLike | undefined;
|
|
31
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
32
|
+
usage = entry.message.usage;
|
|
33
|
+
const input = usage.input ?? 0;
|
|
34
|
+
const cacheRead = usage.cacheRead ?? 0;
|
|
35
|
+
const cacheWrite = usage.cacheWrite ?? 0;
|
|
36
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
37
|
+
totals.latestCacheHitRate = promptTokens > 0 ? (cacheRead / promptTokens) * 100 : undefined;
|
|
38
|
+
} else if (entry.type === "message" && entry.message.role === "toolResult") {
|
|
39
|
+
usage = entry.message.usage;
|
|
40
|
+
} else if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
41
|
+
usage = entry.usage;
|
|
42
|
+
}
|
|
43
|
+
if (!usage) continue;
|
|
44
|
+
|
|
45
|
+
totals.input += usage.input ?? 0;
|
|
46
|
+
totals.output += usage.output ?? 0;
|
|
47
|
+
totals.cacheRead += usage.cacheRead ?? 0;
|
|
48
|
+
totals.cacheWrite += usage.cacheWrite ?? 0;
|
|
49
|
+
totals.cost += usage.cost?.total ?? 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return totals;
|
|
53
|
+
}
|