SLOPSHOPPER

usage-alert

Shows 5-hour and weekly usage in the status line and alerts when either reaches 90%

newbandtoaststatus
★ 1v0.1.0no licenseupdated 2026-10-05CrAzyScreamx/claude-marketplace/mods/usage-alert
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-alert
› 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 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ usage-alert: 5-hour 31%
Source 2 files
hooks/register.tsx 70 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Limit } from '../types'
5
6const THRESHOLD = 90
7
8const limits = atom({ plugin: 'usage-alert', key: 'limits' } as const, [] as Limit[])
9
10const LABELS: Record<string, string> = {
11  five_hour: '5-hour',
12  seven_day: 'weekly',
13}
14
15const label = (kind: string) => LABELS[kind] ?? kind
16
17export const register: Register = on => {
18  // Windows already alerted on; cleared when a window drops below the threshold.
19  const alerted = new Set<string>()
20
21  on('session.measure', async ($, e, next) => {
22    const current: Limit[] = e.rateLimits.map(r => ({
23      kind: r.kind,
24      percentUsed: r.percentUsed,
25    }))
26    await update($, limits, () => current)
27
28    const hot = current.filter(r => r.percentUsed >= THRESHOLD)
29
30    for (const r of current) {
31      if (r.percentUsed < THRESHOLD) {
32        alerted.delete(r.kind)
33      }
34    }
35
36    for (const r of hot) {
37      if (!alerted.has(r.kind)) {
38        alerted.add(r.kind)
39        $.ui.toast(`⚠️ ${label(r.kind)} limit at ${Math.round(r.percentUsed)}%`)
40      }
41    }
42
43    const summary = current
44      .map(r => `${label(r.kind)} ${Math.round(r.percentUsed)}%`)
45      .join(' · ')
46    const alert = hot.length > 0 ? '⚠️ ' : ''
47    $.ui.status(summary === '' ? undefined : `${alert}${summary}`)
48
49    return next(e)
50  })
51
52  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
53    const hot = (await read($, limits)).filter(r => r.percentUsed >= THRESHOLD)
54
55    if (hot.length === 0) {
56      return next(e)
57    }
58
59    const { Box, Text } = $.ui.resolve(e)
60
61    return (
62      <Box>
63        <Text color="red" bold>
64          ⚠️ {hot.map(r => `${label(r.kind)} ${Math.round(r.percentUsed)}%`).join(' · ')}
65        </Text>
66      </Box>
67    )
68  })
69}
70
types/index.d.ts 8 lines
1export type Limit = { kind: string; percentUsed: number }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'usage-alert': { limits: Limit[] }
6  }
7}
8