SLOPSHOPPER

usage-both

Always show both the 5-hour and weekly Claude usage, with colored progress bars, above the prompt

newband
v0.6.4MITupdated 2026-10-09paulpc2/claude-code-mods/mods/usage-both
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-both
› 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 5小時 已用 31% --:-- 重置 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
5小時 已用 31% --:-- 重置
README

usage-both

在 Claude Code 輸入框上方,固定顯示「5 小時」與「每週」兩個用量額度。

輸入框上方同時顯示 5 小時與每週用量:彩色百分比標籤、分三段的進度條、重置時間

功能

  • 5 小時與每週用量並排在一行,5 小時固定在前,額度用滿時也照常顯示
  • 百分比以彩色標籤顯示,進度條撐滿整行寬度
  • 顏色分三階段:綠色(低於 60%)、橘色(60% 到 79%)、紅色(80% 以上);進度條底色也分成同樣三段,段與段之間留有刻度
  • 重置時間顯示幾點幾分,依你電腦的時區;超過一天會加上日期
  • 剛開啟或重新載入時,先顯示上次的數字,不會閃成 0%
  • 介面語言可選繁體中文或英文(見下方「切換語言」)

安裝

需要支援 mod(外掛 hooks)的 Claude Code 版本,終端機版與桌面版 Code 分頁都可用。

以下兩行指令要在「終端機」執行(Mac 內建的「終端機」App,或任何終端機),不是貼到 Claude Code 的對話框。一次貼一行,按 Enter。

  1. 加入來源
claude plugin marketplace add paulpc2/claude-code-mods
  1. 安裝外掛
claude plugin install usage-both@paulpc2-mods
  1. 回到 Claude Code,在對話框輸入 /reload-plugins,或完全關閉後重新開啟 Claude Code。

輸入框上方出現兩條用量,就代表成功。安裝時看到「1 userConfig option not yet set」可以忽略,那是語言選項,預設是繁體中文。

切換語言(Language)

預設是繁體中文。想改成英文,請照下面三個步驟做。

桌面版的外掛頁面沒有語言選項,所以要改設定檔。

  1. 在「終端機」執行,用「文字編輯」打開設定檔:
open -e ~/.claude/settings.json
  1. 在檔案裡加入 env 這一段:
{
  "env": { "USAGE_BOTH_LANGUAGE": "en" }
}

如果檔案裡已經有 env,只要在裡面多加一行 "USAGE_BOTH_LANGUAGE": "en",行與行之間記得加逗號。

  1. 存檔,然後完全關閉並重新開啟 Claude Code(Mac 按 Cmd + Q)。只輸入 /reload-plugins 不會生效。

要改回中文,把值改成 "zh-TW",或直接刪掉那一行,一樣要完全重開。

更新

同樣在「終端機」依序執行:

claude plugin marketplace update paulpc2-mods
claude plugin update usage-both@paulpc2-mods

更新後一樣要完全重開 Claude Code。

限制

  • 用量數字來自 Claude Code 最近一次 API 回應,每回合結束後更新,不是即時輪詢。
  • 只適用於 Claude 訂閱方案;使用 API Key、Bedrock 或 Vertex 時沒有這些額度資料。
  • 依賴 Claude Code 的外掛介面,日後介面變動時可能需要更新。

English

Shows both the 5-hour and weekly Claude usage above the Claude Code prompt.

Above the prompt: 5-hour and weekly usage with colored percentage badges, three-zone progress bars and reset times

  • Both windows on one line, 5-hour first, always visible
  • Colored percentage badge and full-width progress bar: green (< 60%), orange (60–79%), red (≥ 80%); the track is split into the same three zones with tick gaps at 60% and 80%
  • Reset time as clock time in your local time zone, with the date when it is more than a day away
  • Last known figures are shown right after a reload instead of 0%
  • Traditional Chinese (default) or English (see "Language" below)

Install

Run the two commands below in a terminal (the macOS Terminal app or any terminal), not in the Claude Code chat box. Paste one line at a time and press Enter.

  1. Add the source
claude plugin marketplace add paulpc2/claude-code-mods
  1. Install the plugin
claude plugin install usage-both@paulpc2-mods
  1. Back in Claude Code, type /reload-plugins in the chat box, or quit and reopen Claude Code.

When two usage rows appear above the prompt, it worked. You can ignore the "1 userConfig option not yet set" note shown during install; it refers to the language option, which defaults to Traditional Chinese.

Language

The default is Traditional Chinese. To switch to English, follow the three steps below.

The desktop app's plugin page has no language option, so the setting goes in a config file.

  1. In a terminal, open the settings file in TextEdit:
open -e ~/.claude/settings.json
  1. Add this env block:
{
  "env": { "USAGE_BOTH_LANGUAGE": "en" }
}

If the file already has an env block, just add "USAGE_BOTH_LANGUAGE": "en" inside it, with a comma between entries.

  1. Save, then quit and reopen Claude Code completely (on a Mac press Cmd + Q). Running /reload-plugins alone does not apply it.

To switch back, set the value to "zh-TW" or delete the line, then restart the same way.

Update

In a terminal, run these in order:

claude plugin marketplace update paulpc2-mods
claude plugin update usage-both@paulpc2-mods

Then quit and reopen Claude Code.

License

MIT

Source 2 files
hooks/register.tsx 137 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Limit } from '../types'
5
6const limits = atom({ plugin: 'usage-both', key: 'limits' } as const, [] as Limit[])
7
8const KINDS = ['five_hour', 'seven_day']
9type Lang = 'zh-TW' | 'en'
10
11const TEXT = {
12  'zh-TW': { five_hour: '5小時', seven_day: '每週', used: '已用', reset: '重置' },
13  en: { five_hour: '5h', seven_day: 'Weekly', used: 'used', reset: 'reset' },
14} as const
15
16const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
17
18const pad = (n: number) => String(n).padStart(2, '0')
19
20// 重置時間一律顯示幾點幾分(使用這台電腦的時區);一天以上才加日期
21const reset = (lang: Lang, iso?: string) => {
22  const word = TEXT[lang].reset
23  const t = iso ? Date.parse(iso) : NaN
24  if (!(t > 0)) return `--:-- ${word}`
25  const d = new Date(t)
26  const hm = `${pad(d.getHours())}:${pad(d.getMinutes())}`
27  if (t - Date.now() < 86400000) return `${hm} ${word}`
28  return lang === 'en'
29    ? `${MONTHS[d.getMonth()]} ${d.getDate()} ${hm} ${word}`
30    : `${d.getMonth() + 1}月${d.getDate()}日 ${hm} ${word}`
31}
32
33// 綠 < 60%,橘 60–79%,紅 ≥ 80%(用量);底色不透明度 80%(#CC)
34const tone = (pct: number) => (pct >= 80 ? '#D92D2DCC' : pct >= 60 ? '#E8820CCC' : '#2A9D45CC')
35
36// 進度條底色分三段(0–60、60–80、80–100),段與段之間留一點空隙當刻度(約軌道寬度 0.33%);底色用 25% 不透明
37const ZONES: [number, number, string][] = [
38  [0, 60, '#2A9D4540'],
39  [60, 80, '#E8820C40'],
40  [80, 100, '#D92D2D40'],
41]
42
43async function refresh($: any) {
44  const u = await $.session.usage()
45  const fresh: Limit[] = u.rateLimits.map((r: Limit) => ({
46    kind: r.kind,
47    percentUsed: r.percentUsed,
48    resetsAt: r.resetsAt,
49  }))
50  // 剛重新載入時還沒有新讀數:先用上次存下的數字,不顯示 0%
51  let saved: Limit[] = []
52  try {
53    saved = JSON.parse(String((await $.store.get('limits')) ?? '[]'))
54  } catch {}
55  if (fresh.length > 0) await $.store.set('limits', JSON.stringify(fresh))
56  await update($, limits, (cur: Limit[]) => {
57    const prev = cur.length > 0 ? cur : saved
58    const next = [...fresh]
59    // 視窗暫時從回報消失(例如額度用完)時,保留上一次的數字,不讓它消失
60    for (const p of prev) {
61      if (!KINDS.includes(p.kind) || next.some(n => n.kind === p.kind)) continue
62      const isExpired = p.resetsAt !== undefined && Date.parse(p.resetsAt) <= Date.now()
63      next.push(isExpired ? { kind: p.kind, percentUsed: 0 } : p)
64    }
65    return next
66  })
67}
68
69export const register: Register = (on, options) => {
70  const optionLang: Lang = options.language === 'en' ? 'en' : 'zh-TW'
71
72  on('session.start', async ($, e, next) => {
73    await refresh($)
74    return next(e)
75  })
76
77  on('session.measure', async ($, e, next) => {
78    await refresh($)
79    return next(e)
80  })
81
82  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
83    if (e.props.hasSurvey) return next(e)
84    const list = await read($, limits)
85
86    // 語言:環境變數 USAGE_BOTH_LANGUAGE 優先(桌面版不一定會把外掛設定傳進來),否則用外掛設定
87    let envLang: string | undefined
88    try {
89      envLang = await $.env.get('USAGE_BOTH_LANGUAGE')
90    } catch {}
91    const lang: Lang = envLang === 'en' || envLang === 'zh-TW' ? envLang : optionLang
92    const T = TEXT[lang]
93
94    const { Box, Text } = $.ui.resolve(e)
95    // 兩個額度永遠都畫,沒有資料時用空條佔位;5 小時固定在前
96    const rows = KINDS.map(k => list.find(l => l.kind === k) ?? { kind: k, percentUsed: 0 })
97
98    // 兩段平分整列寬度;文字(含重置時間)完整顯示,進度條撐滿剩下的空間
99    return (
100      <Box flexDirection="row" alignItems="center" width="100%">
101        {rows.map((l, i) => {
102          const used = Math.max(0, Math.min(100, l.percentUsed))
103          return (
104            <Box key={l.kind} flexDirection="row" alignItems="center" flexGrow={1} marginLeft={i === 0 ? 0 : 4}>
105              <Text bold>{T[l.kind as 'five_hour' | 'seven_day']}</Text>
106              <Text>  </Text>
107              <Text bold>{T.used}</Text>
108              <Text> </Text>
109              <Text bold color="#FFFFFF" backgroundColor={tone(l.percentUsed)}> {l.percentUsed}% </Text>
110              <Text>  </Text>
111              <Box flexDirection="row" flexGrow={1} height={1} alignItems="center">
112                {ZONES.flatMap(([a, b, tint], z) => {
113                  const filled = Math.max(0, Math.min(used, b) - a)
114                  const empty = b - a - filled
115                  return [
116                    // 刻度:1% 寬的格子,左邊三分之二塗上前一段的顏色,只留右邊三分之一(約 0.33%)透明
117                    z > 0 && (
118                      <Box key={`g${z}`} width="1%" height="60%" flexDirection="row">
119                        <Box width="67%" height="100%" backgroundColor={used >= a ? tone(l.percentUsed) : ZONES[z - 1][2]} />
120                      </Box>
121                    ),
122                    filled > 0 && <Box key={`f${z}`} flexGrow={filled} height="60%" backgroundColor={tone(l.percentUsed)} />,
123                    empty > 0 && <Box key={`e${z}`} flexGrow={empty} height="60%" backgroundColor={tint} />,
124                  ]
125                })}
126              </Box>
127              <Box flexShrink={0}>
128                <Text bold>  {reset(lang, l.resetsAt)}</Text>
129              </Box>
130            </Box>
131          )
132        })}
133      </Box>
134    )
135  })
136}
137
types/index.d.ts 8 lines
1export type Limit = { kind: string; percentUsed: number; resetsAt?: string }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'usage-both': { limits: Limit[] }
6  }
7}
8