SLOPSHOPPER

usage-meter

Live token/cost band above the prompt, with /warm-cache keep-alive counter.

newbandcommand
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-meter
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /warm-cache ⎿ usage-meter: Cache keep-warm ping #1 recorded. USAGE 49% 97.4k / 200k warm:1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
USAGE 49% 97.4k / 200k warm:1
README

usage-meter

Live token/cost band above the prompt, with /warm-cache keep-alive counter.

Install

claude plugin marketplace add kenhuangus/claude-code-mods-pack
claude plugin install usage-meter@kenhuang-mods
Source 2 files
hooks/usage-meter.mjs 59 lines
1const SNAP = { plugin: "usage-meter", key: "snapshot" };
2const WARM = { plugin: "usage-meter", key: "warmPings" };
3function short(n) {
4  if (n >= 1_000_000) return `${+(n / 1_000_000).toFixed(1)}M`;
5  if (n >= 1_000) return `${+(n / 1_000).toFixed(1)}k`;
6  return String(n);
7}
8async function readUsage($) {
9  const usage = await $.session.usage();
10  const context = usage?.context;
11  if (!context?.window) return null;
12  const tokens = context.tokens ?? 0;
13  const percent = context.percent ?? Math.round((tokens / context.window) * 100);
14  const costUsd = typeof usage?.cost?.totalUsd === "number" ? usage.cost.totalUsd : undefined;
15  return { tokens, window: context.window, percent, costUsd };
16}
17export function register(on) {
18  on("session.start", async ($, e, next) => {
19    const r = await next(e);
20    await $.state.set(WARM, 0);
21    const snap = await readUsage($);
22    if (snap) await $.state.set(SNAP, snap);
23    await $.command.register({ name: "warm-cache", description: "Record a prompt-cache keep-warm ping" });
24    return r;
25  });
26  on("turn.complete", async ($, e, next) => {
27    const r = await next(e);
28    if (!e.agentId) {
29      const snap = await readUsage($);
30      if (snap) await $.state.set(SNAP, snap);
31    }
32    return r;
33  });
34  on("command.run", { command: "warm-cache" }, async ($) => {
35    const { value: n = 0 } = await $.state.get(WARM);
36    const nextN = n + 1;
37    await $.state.set(WARM, nextN);
38    const snap = (await readUsage($)) || { tokens: 0, window: 200000, percent: 0 };
39    await $.state.set(SNAP, snap);
40    return { text: `Cache keep-warm ping #${nextN} recorded.` };
41  });
42  on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
43    if (e.props?.hasSurvey) return next(e);
44    const { value: snap } = await $.state.get(SNAP);
45    const { value: warm = 0 } = await $.state.get(WARM);
46    if (!snap) return next(e);
47    const { Box, Text } = $.ui.resolve(e);
48    const cost = typeof snap.costUsd === "number" ? `  $${snap.costUsd.toFixed(3)}` : "";
49    return Box({
50      flexDirection: "row",
51      paddingX: 1,
52      children: [
53        Text({ color: snap.percent >= 85 ? "red" : snap.percent >= 60 ? "yellow" : "green", bold: true, children: `USAGE ${snap.percent}%` }),
54        Text({ dimColor: true, children: `  ${short(snap.tokens)} / ${short(snap.window)}${cost}  warm:${warm}` }),
55      ],
56    });
57  });
58}
59
types/index.d.ts 9 lines
1declare module "claude-code" {
2  interface PluginState {
3    "usage-meter": {
4      snapshot: { tokens: number; window: number; percent: number; costUsd?: number };
5      warmPings: number;
6    };
7  }
8}
9