SLOPSHOPPER

usage-bar

Usage bars (context, 5h, 7d, cost) above the prompt plus an auto-compact nudge; toggle with /usage-bar

newbandcommandtoast
v0.1.0no licenseupdated 2026-10-03richardcsuwandi/claude-mods/plugins/usage-bar
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-bar
› 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 › /usage-bar ⎿ usage-bar: Usage bar off. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

claude-mods

Small Claude Code mods I use every day. Each one is a few dozen lines of TypeScript and installs like any plugin.

/plugin marketplace add richardcsuwandi/claude-mods
/plugin install <mod>@claude-mods
/reload-plugins
ModWhat it does
deadlinesLive deadline countdowns in the status line, /ddl to manage them
usage-barContext, 5h, 7d and cost bars above the prompt, plus an auto-compact nudge
context-barA stacked context-window bar, colored like /context
monitorA control room above the prompt: background agents and shells, blocked tool calls, context

Mods run with the same access to your machine as Claude Code itself and aren't sandboxed. Read the source (each is one file in plugins/<mod>/hooks/) before you install.

The images below are previews drawn from each mod's real output format and color thresholds, not captures of a live session (the monitor one is animated). Your terminal's font and theme will differ.


deadlines

deadlines preview

Keeps your upcoming deadlines in the status line and shows all of them on demand. The status line holds the next one with a live countdown (days, hours, minutes, refreshed every 15 seconds), and +N counts the rest.

/ddl                                          list everything as a card
/ddl add AISTATS 2026-10-06 aoe               add a deadline (time defaults to 23:59)
/ddl add Group meeting 2026-10-16 14:00       no zone = your local time
/ddl add ICML 2027-01-28 23:59 pst            any zone from the table below
/ddl add Thesis 2026-12-01 17:00 wib          Indonesia (WIB, WITA, WIT all work)
/ddl add Review 2026-11-03 09:00 +05:30       or a plain UTC offset (also utc-5, gmt+7)
/ddl rm AISTATS                               remove one

Whatever zone you type, the deadline is stored as an exact moment and shown in your own local time, so the countdown is always right. The confirmation echoes both, for example Added ICML: Fri Jan 29 12:00 CST (2027-01-28 23:59 PST, UTC-08:00).

Time zones

RegionZones
Anywhereaoe (UTC-12, "Anywhere on Earth"), utc, gmt, local (your machine)
US and Canadapst pdt mst mdt cst cdt est edt akst akdt hst ast nst, plus pt mt ct et, which pick standard or daylight time from the deadline's date
Europewet west bst cet cest eet eest msk
Asiaist (India) pkt npt ict wib wita wit sgt hkt pht jst kst
Oceaniaawst acst aest aedt nzst nzdt
Any otheran offset such as +08:00, -0530, +7, utc+7, gmt-5

Some abbreviations are ambiguous. cst means your own zone if that's what your machine reports (so it is China Standard Time in Shanghai) and US Central otherwise. ist is India. For anything else, use an offset. An unknown zone is refused with a message and nothing is saved.

pt, et, ct and mt follow the US rule (second Sunday of March to first Sunday of November). cet is always UTC+1, so use cest for European summer deadlines.

How to read it

Time leftEmojiStatus glyphColor
over 30 days🌱○green
7 to 30 days🗓○green
3 to 7 days⏳◐amber
1 to 3 days😬◐amber
under 1 day🔥●red

The bar in each row fills up over the last 30 days. Deadlines that have passed stay in the list, greyed out with a ✅, until you rm them. Names can contain spaces.

Where your deadlines are saved

In Claude Code's per-plugin store (~/.claude/plugins/store/deadlines_<marketplace>-<hash>.json). The file name depends on the plugin and marketplace names only, so updating the plugin keeps your list. A second copy is mirrored to ~/.claude/deadlines-backup.json: if the store ever comes back empty or unreadable, /ddl restores from that copy instead of overwriting it, and keeps the unreadable value under deadlines.corrupt. Renaming the marketplace creates a fresh store, which is what the backup covers. Persistence tests and time zone parsing live in plugins/deadlines/tests/ (run them with claude plugin test plugins/deadlines).

Notes

  • Times shown are in your machine's timezone, read once when the session starts. A daylight-saving change mid-session is off by an hour until you restart.
  • If the timezone can't be read (for example on Windows), times show in UTC.

usage-bar

usage-bar preview

Three bars above the prompt: how full the context window is (ctx), and your 5-hour and 7-day usage limits, followed by the session cost in dollars. Bars turn amber at 70% and red at 90%. The set of limit bars depends on what your plan reports.

Auto-compact nudge. When the context passes 85% a toast tells you once to run /compact, and a Compact button appears next to the bars. It re-arms after the context drops back under 75%, so a compact doesn't silence the next warning. The nudge keeps working even while the bars are hidden.

/usage-bar        toggle the bars (on by default)

Updates after every turn.


context-bar

context-bar preview

One stacked bar showing what's filling the context window: system prompt, tools, memory files, messages, with the free space and the auto-compact buffer shown as dim blocks at the end. The colors match the ones /context uses, and a legend underneath lists each category with its size in tokens.

/context-bar      toggle the bar (off by default)

Updates after every turn using Claude Code's local token estimates, so it makes no extra API calls and costs no tokens. The bar resizes to your terminal width.

The categories in the preview are illustrative: yours come from your own session.


monitor

monitor preview

A small control room above your prompt for everything Claude is doing in the background. When Claude starts a background agent or a shell with run_in_background, a rounded card appears with one row per job:

RowMeaning
🤖 / ⚙️ blue name, sweeping bar, elapsed timea background agent or shell that is still running. The bar shows activity, not progress
✅ dim green donethe job finished. Stays until your next prompt, so you don't miss it
❌ red failed needs youthe job failed (non-zero exit, or the agent failed). Stays until your next prompt
⏹ amber stoppedthe job was killed, by you or by Claude
🚫 amber Bash blocked: ...a hook or permission refused a tool call, with the reason. Cleared at your next prompt
🧠 contexthow full the context window is: amber at 70%, red at 90%, and compact from 85% as a reminder to run /compact

The header counts them (Monitor 1 running 1 done 1 failed). With nothing running and the context low, the card doesn't draw at all, so it never takes space when idle. It stacks with the bars above if you use usage-bar or context-bar too.

/monitor        pin the card so the context row is always visible; run again to unpin
                (unpinning also clears finished jobs)

Try it. Ask Claude: "Run sleep 30 and sleep 45 as background jobs, and start one tiny haiku subagent that sleeps 20 seconds." Add exit 1 to one of them to see a failure.

How it works. It watches Agent and Bash calls from the main session. A shell counts as a job when its result carries a background task id, and an agent when it comes back async_launched. A job ends when Claude Code delivers the task notification for its id, and the notification's status (completed, failed, killed) picks the row. Tests: claude plugin test plugins/monitor.

Notes

  • Finished rows show no duration. The notification arrives when Claude is free to read it, not when the job ended, so any time would be misleading.
  • An agent that stops while its own background work is still running notifies once at that point, and again when it is really done. The row turns ✅ at the first notification, so it can read done slightly early.
  • Subagents' own tool calls aren't tracked, only the jobs the main session starts.
  • Inspired by leitstand (a control room for Claude Code). This is an independent implementation with a different look and no disk row.

Adding a mod

Put it in plugins/<name>/ (manifest in .claude-plugin/plugin.json, hooks in hooks/), add an entry to .claude-plugin/marketplace.json, run claude plugin validate, and push.

Source 2 files
hooks/register.tsx 85 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Snap } from '../types'
5
6const NUDGE_AT = 85
7const isOn = atom({ plugin: 'usage-bar', key: 'isOn' } as const, true)
8const snap = atom({ plugin: 'usage-bar', key: 'snap' } as const, null)
9const isNudged = atom({ plugin: 'usage-bar', key: 'isNudged' } as const, false)
10
11const LABELS: Record<string, string> = { five_hour: '5h', seven_day: '7d', spend_limit: 'spend' }
12
13async function refresh($: any) {
14  const { context, rateLimits, cost } = await $.session.usage()
15  const next: Snap = {
16    bars: [
17      { label: 'ctx', percent: context.percent ?? 0 },
18      ...rateLimits.map((r: any) => ({ label: LABELS[r.kind] ?? r.kind, percent: r.percentUsed })),
19    ],
20    usd: cost ? cost.usd : null,
21  }
22  await update($, snap, () => next)
23
24  const ctx = next.bars[0].percent
25  if (ctx >= NUDGE_AT && !(await read($, isNudged))) {
26    await update($, isNudged, () => true)
27    $.ui.toast(`Context ${Math.round(ctx)}% full. Run /compact (or press Compact above the prompt).`)
28  } else if (ctx < NUDGE_AT - 10) {
29    await update($, isNudged, () => false) // re-arm after a compact
30  }
31}
32
33export const register: Register = on => {
34  on('session.start', async ($, e, next) => {
35    await $.command.register({ name: 'usage-bar', description: 'Toggle the usage bars above the prompt' })
36    return next(e)
37  })
38
39  on('command.run', { command: 'usage-bar' }, async $ => {
40    const now = !(await read($, isOn))
41    await update($, isOn, () => now)
42    if (now) await refresh($)
43    return { text: `Usage bar ${now ? 'on' : 'off'}.` }
44  })
45
46  // nudge runs even with the bar hidden
47  on('turn.complete', async ($, e, next) => {
48    await refresh($)
49    return next(e)
50  })
51
52  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
53    const s = await read($, snap)
54    if (!(await read($, isOn)) || !s || e.props.hasSurvey) return next(e)
55    const below = await next(e) // other bands stack under this row
56
57    const { Box, Text, Button } = $.ui.resolve(e)
58    const hue = (p: number) => (p >= 90 ? 'red' : p >= 70 ? 'yellow' : 'green')
59    const W = 10
60
61    return (
62      <Box flexDirection="column">
63        {below}
64        <Box>
65          {s.bars.map(b => {
66            const n = Math.min(W, Math.round((b.percent / 100) * W))
67            return (
68              <Box key={b.label}>
69                <Text dimColor>{b.label} </Text>
70                <Text color={hue(b.percent)}>{'█'.repeat(n)}</Text>
71                <Text dimColor>{'░'.repeat(W - n)} </Text>
72                <Text color={hue(b.percent)}>{Math.round(b.percent)}%  </Text>
73              </Box>
74            )
75          })}
76          {s.usd !== null && <Text dimColor>${s.usd.toFixed(2)} </Text>}
77          {s.bars[0].percent >= NUDGE_AT && (
78            <Button key="compact" label="Compact" onPress={() => $.session.compact()} />
79          )}
80        </Box>
81      </Box>
82    )
83  })
84}
85
types/index.d.ts 9 lines
1export type Bar = { label: string; percent: number }
2export type Snap = { bars: Bar[]; usd: number | null }
3
4declare module 'claude-code' {
5  interface PluginState {
6    'usage-bar': { isOn: boolean; snap: Snap | null; isNudged: boolean }
7  }
8}
9