SLOPSHOPPER

usage-meter

Current session (5h) and weekly usage as progress bars above the prompt

newband
v0.1.0no licenseupdated 2026-10-14felixchen-tw/claude-config/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 Session █████░░░░░░░░░░ 31% 重置 10/9 09:53 本週 (尚無資料) ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
Session █████░░░░░░░░░░ 31% 重置 10/9 09:53 本週 (尚無資料) ⟨Claude Code's own drawing⟩
README

~/.claude 設定同步

新裝置:

git clone git@github.com:felixchen-tw/claude-config.git ~/.claude   # ~/.claude 已存在就先移開,再把憑證搬回來
git clone https://github.com/Raymondhou0917/ai-level-check ~/ai-level-check && ln -s ~/ai-level-check ~/.claude/skills/ai-level-check

啟動 Claude Code 後,settings.json 裡的 plugin 會自動安裝。 felix-mods 的 marketplace 用的是絕對路徑 /home/felix/.claude/mods,家目錄不同(例如 macOS 的 /Users/felix)要改 settings.json。 skills/ 裡的 caveman 系列與 find-skills 是從 npx skills 複製進來的實體檔(不再 symlink 到 ~/.agents),要更新就重跑 npx skills add JuliusBrussee/caveman -g 後再複製進來。 claude.ai 內建的 skills(skills/synced/)跟著帳號同步,不用處理。

同步:git -C ~/.claude pull / git -C ~/.claude commit -am ... && git push

Source 2 files
hooks/register.tsx 72 lines
1import { atom, read, update } from 'claude-code'
2import type { Register, SessionRateLimit } from 'claude-code'
3
4import type { Usage } from '../types'
5
6const usage = atom({ plugin: 'usage-meter', key: 'usage' } as const, null)
7
8export const bar = (pct: number, width = 15) => {
9  const n = Math.round((Math.min(Math.max(pct, 0), 100) / 100) * width)
10  return '█'.repeat(n) + '░'.repeat(width - n)
11}
12
13const fmtDate = (iso: string) => {
14  const d = new Date(iso)
15  return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
16}
17
18// Green while plenty is left, yellow past half used, red past 80%.
19export const tone = (pct: number) => (pct >= 80 ? 'red' : pct >= 50 ? 'yellow' : 'green')
20
21// Account-wide windows, so a new conversation does not reset them.
22const toUsage = (rateLimits: SessionRateLimit[]): Usage => {
23  const five = rateLimits.find(r => r.kind === 'five_hour')
24  const week = rateLimits.find(r => r.kind === 'seven_day')
25  return {
26    sessionPercent: five?.percentUsed,
27    sessionResetsAt: five?.resetsAt,
28    weekPercent: week?.percentUsed,
29    weekResetsAt: week?.resetsAt,
30  }
31}
32
33export const register: Register = on => {
34  on('session.start', async ($, e, next) => {
35    const { rateLimits } = await $.session.usage()
36    await update($, usage, () => toUsage(rateLimits))
37    return next(e)
38  })
39
40  on('session.measure', async ($, e, next) => {
41    await update($, usage, () => toUsage(e.rateLimits))
42    return next(e)
43  })
44
45  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
46    const u = await read($, usage)
47    if (e.props.hasSurvey || u === null) return next(e)
48
49    const below = await next(e)
50    const { Box, Text } = $.ui.resolve(e)
51    const meter = (pct?: number, resetsAt?: string) =>
52      pct === undefined
53        ? <Text dimColor>(尚無資料)</Text>
54        : <Text>
55            <Text color={tone(pct)}>{bar(pct)} {pct.toFixed(0)}%</Text>
56            <Text dimColor>{resetsAt ? ` 重置 ${fmtDate(resetsAt)}` : ''}</Text>
57          </Text>
58
59    return (
60      <Box flexDirection="column">
61        <Text>
62          <Text dimColor>Session </Text>
63          {meter(u.sessionPercent, u.sessionResetsAt)}
64          <Text dimColor>  本週 </Text>
65          {meter(u.weekPercent, u.weekResetsAt)}
66        </Text>
67        {below}
68      </Box>
69    )
70  })
71}
72
types/index.d.ts 13 lines
1export type Usage = {
2  sessionPercent?: number
3  sessionResetsAt?: string
4  weekPercent?: number
5  weekResetsAt?: string
6}
7
8declare module 'claude-code' {
9  interface PluginState {
10    'usage-meter': { usage: Usage | null }
11  }
12}
13