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

新裝置:
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
hooks/register.tsx 72 lines1import { 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}
72types/index.d.ts 13 lines1export 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