SLOPSHOPPER

usage-meter

Minimal band: model, context tokens and estimated session cost, coloured by context fill

newbandstatusprompt
v0.3.0no licenseupdated 2026-10-08KingMichaelPark/claude-mods/usage-meter
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 󱜙 claude-opus-5-5 · 97k/200k (49%) ·  0.42 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
󱜙 claude-opus-5-5 · 97k/200k (49%) ·  0.42
README

Claude Mods

A curated collection of lightweight, high-impact plugins for Claude Code designed to give you real-time visibility into session consumption and automatically optimize your token usage and API costs.


📦 Included Plugins

PluginDescriptionKey Highlight
usage-meterReal-time prompt header displaying model, context window capacity, and session cost.Color-coded context fill alerts & instant post-compaction estimates.
model-routerAutonomous model routing for spawned sub-agents based on task complexity.Keeps the main thread on your preferred model while routing sub-tasks to the cheapest capable tier.

📊 usage-meter

Adds an always-visible, minimal status bar positioned directly above the input prompt (AbovePrompt).

󱜙  opus-5-5 · 84k/1.0M (42%) ·  $1.23

Features

  • Context Window Monitor: Tracks token usage against total window capacity with percentage indicators.
  • Color-Coded Thresholds:
  • 🟢 Green (< 40%): Safe zone; ample room for extended context.
  • 🟡 Amber (40% - 69%): Moderate usage; considerations for upcoming compaction.
  • 🔴 Red (≥ 70%): Heavy fill; approaching window limits.
  • Smart Compaction Tracking: Immediately renders estimated sizes (~31k/200k (~16%)) right after /compact without waiting for the next turn measurement.
  • Session Cost Counter: Real-time USD spend display updated as prompts complete.
  • Instant Model Switching: Reacts immediately whenever /model is changed.

⚡ model-router

Optimizes costs and execution speed by dynamically routing sub-agents to the most cost-effective Claude model tier (opus, sonnet, or haiku).

How It Works

  1. Protects the Main Thread: The main conversation thread remains untouched on whatever /model you configure. This prevents cache thrashing and ensures high-level problem understanding stays consistent.
  2. Evaluates Sub-Agent Tasks: When a sub-agent spawns without an explicit model override, a fast Haiku classifier evaluates the sub-agent prompt and task description against a strict rubric.
  3. Runs on the Cheapest Capable Tier:
  4. haiku: Fast, lightweight lookups, file/symbol searches, listing usages, reading config files, summarizing command output, formatting, and one-line changes.
  5. sonnet: Standard feature work, bug fixes with clear causes, writing unit tests, code reviews, and routine refactorings.
  6. opus: Open-ended architecture, multi-file refactors, subtle concurrency/performance debugging, security-critical changes, and complex multi-step planning.
  7. Toast Alerts: Displays a subtle toast whenever a sub-agent is routed to a different tier than the main thread.
  8. Usage & Token Telemetry: Aggregates lifetime spawn counts, input/output tokens, and prompt cache hit/miss stats across tiers.

Slash Commands (/route)

Manage sub-agent routing behaviour in Claude Code using the /route command:

# Set routing mode
/route auto           # Haiku intelligently picks the model per sub-agent (default)
/route off            # Disables routing; sub-agents inherit the main thread model
/route sonnet         # Force all sub-agents to a specific tier (opus | sonnet | haiku)

# Check status & telemetry
/route status         # Displays the current mode and quick help
/route stats          # Shows spawn counts, token breakdowns, and recent decisions
/route reset-stats    # Clears historical routing statistics

Example stats output:

Sub-agent routing stats (all sessions)
  opus          3 spawns   in   12.4k   out    1.8k   cache read   48.2k   cache write    6.1k
  sonnet       18 spawns   in   64.1k   out   14.2k   cache read  180.5k   cache write   12.0k
  haiku        42 spawns   in   89.3k   out    8.7k   cache read  310.0k   cache write    4.2k

Latest decisions (model, why, agent):
  haiku   router    general-purpose: Find call sites of parseConfig
  sonnet  router    general-purpose: Add unit tests for auth middleware

🚀 Getting Started

1. Directory Setup

This repository is organized as a Claude Code plugin marketplace (mike-mods) defined in .claude-plugin/marketplace.json.

claude-mods/
├── .claude-plugin/
│   └── marketplace.json      # Marketplace manifest declaring both plugins
├── model-router/
│   ├── hooks/
│   │   ├── hooks.json        # Hook entrypoints (register.ts)
│   │   ├── register.ts       # Router implementation & command handling
│   │   └── router.test.ts    # Comprehensive test suite
│   └── types/
│       └── index.d.ts        # TypeScript declarations & state augmentations
└── usage-meter/
    ├── hooks/
    │   ├── hooks.json        # Hook entrypoints (register.tsx)
    │   ├── register.tsx      # UI hook & event subscriptions
    │   ├── format.ts         # Formatting & color thresholds
    │   └── format.test.ts    # Unit tests
    └── types/
        └── index.d.ts        # TypeScript declarations

2. Loading into Claude Code

You can enable these plugins in your Claude Code environment either via marketplace or by pointing directly to the plugin directories.

Option A: Local Marketplace

Add this repository directory as a plugin source in your Claude Code configuration:

# Add this directory as a plugin marketplace or install individual plugins
claude plugin add ./model-router
claude plugin add ./usage-meter
Option B: Project Configuration

To enable these plugins automatically for a specific project, you can reference them in your workspace's .claude/config.json or plugin manifest:

{
  "plugins": [
    "/path/to/claude-mods/model-router",
    "/path/to/claude-mods/usage-meter"
  ]
}

🧪 Running Tests

Both plugins include test suites using the claude-code/testing framework:

  • model-router/hooks/router.test.ts: Verifies routing logic, explicit model overrides, fork handling, toast triggering, mode toggling, and stats accumulation.
  • usage-meter/hooks/format.test.ts: Verifies context string formatting, post-compaction estimate indicators, cost formatting, and color threshold calculations.

🛠️ Requirements

  • Claude Code CLI
  • Nerd Font or Unicode-compatible terminal (for model icon 󱜙 and cost icon )
Source 3 files
hooks/register.tsx 87 lines
1import { atom, read, update } from "claude-code";
2import type { EngineInterface, Register, SessionUsage } from "claude-code";
3
4import { colour, meter } from "./format";
5
6const current = atom({ plugin: "usage-meter", key: "meter" } as const, null);
7
8// The size after a compaction, shown until the next response measures the real one.
9let estimate: number | undefined;
10
11const refresh = async (
12    $: EngineInterface,
13    usage?: Pick<SessionUsage, "context" | "cost">,
14) => {
15    const u = usage ?? (await $.session.usage());
16    const context =
17        estimate === undefined
18            ? u.context
19            : {
20                ...u.context,
21                tokens: estimate,
22                percent: Math.round((estimate / u.context.window) * 100),
23            };
24    const next = meter(
25        await $.session.model(),
26        { ...u, context },
27        estimate !== undefined,
28    );
29    await update($, current, () => next);
30};
31
32export const register: Register = (on) => {
33    on("session.start", async ($, e, next) => {
34        const result = await next(e);
35        estimate = undefined;
36        $.ui.status(undefined);
37        await refresh($);
38        return result;
39    });
40
41    on("session.measure", async ($, e, next) => {
42        estimate = undefined;
43        await refresh($, e);
44        return next(e);
45    });
46
47    // /compact sends no response, so the last measured size is stale until the next one.
48    on("session.compact", async ($, e, next) => {
49        const result = await next(e);
50        if (
51            e.trigger === "precompute" ||
52            result.skip !== undefined ||
53            result.tokensAfter === undefined
54        )
55            return result;
56        estimate = result.tokensAfter;
57        try {
58            await refresh($);
59        } catch {
60            $.ui.log("usage-meter: could not refresh after compaction", {
61                to: "debug",
62            });
63        }
64        return result;
65    }).catch(($, e, next) => next(e));
66
67    // A /model switch shows on the next prompt, before any response arrives.
68    on("prompt.submit", async ($, e, next) => {
69        await refresh($);
70        return next(e);
71    }).catch(($, e, next) => next(e));
72
73    on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
74        const m = await read($, current);
75        if (e.props.hasSurvey || m === null) return next(e);
76
77        const { Text } = $.ui.resolve(e);
78        const c = colour(m.percent);
79
80        return c ? (
81            <Text color={c}>{m.text}</Text>
82        ) : (
83            <Text dimColor>{m.text}</Text>
84        );
85    });
86};
87
hooks/format.ts 48 lines
1import type { SessionUsage, ThemeKey } from "claude-code";
2
3import type { Meter } from "../types";
4
5const compact = (n: number) =>
6    n >= 1_000_000
7        ? `${(n / 1_000_000).toFixed(1)}M`
8        : n >= 1000
9            ? `${Math.round(n / 1000)}k`
10            : `${n}`;
11
12const shortModel = (id: string) =>
13    `󱜙  ${id}`
14        .replace(/^claude-/, "")
15        .replace(/-\d{8}$/, "")
16        .replace(/\[1m\]$/, " 1M");
17
18// `estimated` marks a size the engine has not measured yet, such as just after a compaction.
19export const meter = (
20    model: string,
21    usage: Pick<SessionUsage, "context" | "cost">,
22    estimated = false,
23): Meter => {
24    const { tokens, window, percent } = usage.context;
25    const parts = [shortModel(model)];
26    const mark = estimated ? "~" : "";
27    parts.push(
28        tokens === undefined
29            ? `–/${compact(window)}`
30            : `${mark}${compact(tokens)}/${compact(window)} (${mark}${percent ?? 0}%)`,
31    );
32    if (usage.cost) parts.push(`  ${usage.cost.usd.toFixed(2)}`);
33    return {
34        text: parts.join(" · "),
35        percent: tokens === undefined ? -1 : (percent ?? 0),
36    };
37};
38
39// Green below 40%, amber from 40% to below 70%, red from 70%; undefined before the first response.
40export const colour = (percent: number): ThemeKey | undefined =>
41    percent < 0
42        ? undefined
43        : percent < 40
44            ? "success"
45            : percent < 70
46                ? "warning"
47                : "error";
48
types/index.d.ts 8 lines
1export type Meter = { text: string; percent: number }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'usage-meter': { meter: Meter | null }
6  }
7}
8