@pify/usage 0.6.0 → 0.7.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 +4 -2
- package/extensions/usage.ts +25 -3
- package/package.json +1 -1
- package/src/format.ts +19 -0
package/README.md
CHANGED
|
@@ -11,11 +11,13 @@ Spend is invisible until the invoice arrives, and by then you cannot tell which
|
|
|
11
11
|
## Live footer
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
📊 12.3k tok · $0.45
|
|
14
|
+
📊 12.3k tok · $0.45 · ctx ▰▰▰▱▱▱ 34%
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Folded from each message's `usage.cost`, which pi already computes. It survives `/reload` by replaying the session branch rather than keeping a running total in memory.
|
|
18
18
|
|
|
19
|
+
The gauge on the right is how full the context window is — the third mid-session question, next to tokens and cost. It was computed for the `/usage` dashboard but shown only there; now it is live. It appears once there is a window to measure against (so not under `-p`) and turns to `⚠` past 90%, the point where "how full" stops being trivia and becomes a decision. For the breakdown of what filled it, run `/context`.
|
|
20
|
+
|
|
19
21
|
## `/usage`
|
|
20
22
|
|
|
21
23
|
```
|
|
@@ -40,7 +42,7 @@ History counts every usage-bearing entry in pi's session JSONL — assistant tur
|
|
|
40
42
|
|
|
41
43
|
Per-project totals come for free: pi stores sessions one directory per project, so the dashboard can show where the money actually went.
|
|
42
44
|
|
|
43
|
-
## `/usage context`
|
|
45
|
+
## `/context` (or `/usage context`)
|
|
44
46
|
|
|
45
47
|
```
|
|
46
48
|
Context window: 22.6k of 200.0k used (11%)
|
package/extensions/usage.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { readFileSync } from "node:fs";
|
|
|
29
29
|
|
|
30
30
|
import { addRecord, aggregate, recordFromEntry, windowTotals } from "../src/aggregate.ts";
|
|
31
31
|
import { buildBreakdown, formatBreakdown } from "../src/context.ts";
|
|
32
|
-
import { footerText, formatCost, formatTokens, historyBlock, sessionBlock } from "../src/format.ts";
|
|
32
|
+
import { contextGauge, footerText, formatCost, formatTokens, historyBlock, sessionBlock } from "../src/format.ts";
|
|
33
33
|
import { QUOTA_PROVIDERS, fetchQuota, quotaReport, type QuotaResult } from "../src/quota.ts";
|
|
34
34
|
import { redact } from "../src/redact.ts";
|
|
35
35
|
import { scanSessions } from "../src/sessions.ts";
|
|
@@ -62,7 +62,13 @@ export default function usage(pi: ExtensionAPI) {
|
|
|
62
62
|
|
|
63
63
|
function updateFooter(ctx: UiContext): void {
|
|
64
64
|
if (!ctx.hasUI) return;
|
|
65
|
-
|
|
65
|
+
const base = footerText(session);
|
|
66
|
+
if (!base) {
|
|
67
|
+
ctx.ui.setStatus("usage", undefined);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const gauge = contextGauge(contextPct(ctx));
|
|
71
|
+
ctx.ui.setStatus("usage", gauge ? `${base} · ${gauge}` : base);
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
function contextPct(ctx: UiContext): number | null {
|
|
@@ -187,9 +193,15 @@ export default function usage(pi: ExtensionAPI) {
|
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
pi.registerCommand("usage", {
|
|
190
|
-
description: "Token and cost dashboard: /usage [quota]",
|
|
196
|
+
description: "Token and cost dashboard: /usage [context | quota]",
|
|
191
197
|
handler: async (args, ctx) => {
|
|
192
198
|
if (!ctx.hasUI) return;
|
|
199
|
+
if ((args ?? "").trim().toLowerCase() === "context") {
|
|
200
|
+
// Documented since the breakdown landed, but never wired — the handler
|
|
201
|
+
// only knew "quota", so `/usage context` quietly showed the dashboard.
|
|
202
|
+
ctx.ui.notify(contextBreakdown(ctx), "info");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
193
205
|
if ((args ?? "").trim().toLowerCase() === "quota") {
|
|
194
206
|
// The one networked path in this package, and only when asked for.
|
|
195
207
|
// Providers with no key are skipped entirely rather than reported as
|
|
@@ -225,6 +237,16 @@ export default function usage(pi: ExtensionAPI) {
|
|
|
225
237
|
},
|
|
226
238
|
});
|
|
227
239
|
|
|
240
|
+
pi.registerCommand("context", {
|
|
241
|
+
description: "Where the context window went: /context",
|
|
242
|
+
handler: async (_args, ctx) => {
|
|
243
|
+
// The live footer gauge says how full the window is; this says spent on
|
|
244
|
+
// WHAT — the breakdown was built for the /usage dashboard's one-line note
|
|
245
|
+
// but the full bar view had no command to reach it until now.
|
|
246
|
+
if (ctx.hasUI) ctx.ui.notify(contextBreakdown(ctx), "info");
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
228
250
|
pi.registerTool({
|
|
229
251
|
name: "usage_status",
|
|
230
252
|
label: "Usage status",
|
package/package.json
CHANGED
package/src/format.ts
CHANGED
|
@@ -19,6 +19,25 @@ export function footerText(session: UsageTotals): string | undefined {
|
|
|
19
19
|
return `📊 ${formatTokens(session.totalTokens)} tok · ${formatCost(session.cost)}`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const GAUGE_CELLS = 6;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A compact live context gauge for the footer: a filled/empty bar and the
|
|
26
|
+
* percentage of the window in use. The footer already carries tokens and cost;
|
|
27
|
+
* this answers the third question you have mid-session — how close am I to the
|
|
28
|
+
* wall — which was computed for the /usage dashboard but never shown live. It
|
|
29
|
+
* warns once the window is nearly full, because that is when the number stops
|
|
30
|
+
* being trivia and starts being a decision. Empty when there is no window to
|
|
31
|
+
* measure against (e.g. under `-p`, or before the first response).
|
|
32
|
+
*/
|
|
33
|
+
export function contextGauge(pct: number | null): string {
|
|
34
|
+
if (pct === null || !Number.isFinite(pct)) return "";
|
|
35
|
+
const clamped = Math.max(0, Math.min(100, pct));
|
|
36
|
+
const filled = Math.round((clamped / 100) * GAUGE_CELLS);
|
|
37
|
+
const bar = "▰".repeat(filled) + "▱".repeat(GAUGE_CELLS - filled);
|
|
38
|
+
return `${clamped >= 90 ? "⚠ " : ""}ctx ${bar} ${Math.round(clamped)}%`;
|
|
39
|
+
}
|
|
40
|
+
|
|
22
41
|
export function sessionBlock(session: UsageTotals, contextPct: number | null): string {
|
|
23
42
|
const lines = [
|
|
24
43
|
"Session",
|