Show context and 5h rate-limit usage above the prompt

# 1. Clone (ghqのディレクトリ構造に合わせる)
git clone https://github.com/korryu3/dotfiles.git ~/ghq/github.com/korryu3/dotfiles
cd ~/ghq/github.com/korryu3/dotfiles
# 2. Install (Homebrew導入 → パッケージ → シンボリックリンク → セットアップ → macOS設定)
./install.sh
# 個人用パッケージも含める場合
./install.sh --personal
# 3Dパッケージ (colmap, rtabmap, cloudcompare) も含める場合
./install.sh --3d
# 3. Brewfileにないパッケージを削除 (任意)
brew bundle cleanup --file=~/ghq/github.com/korryu3/dotfiles/Brewfile
brew bundle cleanup --file=~/ghq/github.com/korryu3/dotfiles/Brewfile --force # 実際に削除
install.shで自動化できない設定はdocs/manual-setup.mdを参照。
nvim --cleanでクリーン環境なnvimを起動~/.serena/serena_config.yml) の管理(projects リストがPC固有で自動更新されるため要検討)hooks/register.tsx 97 lines1import { atom, read, update } from 'claude-code'
2import type { Register, SessionContextUsage, SessionRateLimit } from 'claude-code'
3
4import type { UsageSnapshot } from '../types'
5
6const snapshot = atom({ plugin: 'usage-band', key: 'snapshot' } as const, null)
7
8const BRAILLE = '⠀⣀⣄⣤⣦⣶⣷⣿'
9const BAR_WIDTH = 8
10
11const toSnapshot = (
12 context: SessionContextUsage,
13 rateLimits: SessionRateLimit[],
14): UsageSnapshot => {
15 const five = rateLimits.find(limit => limit.kind === 'five_hour')
16
17 return {
18 ctxPercent: context.percent,
19 fiveHour: five && { percentUsed: five.percentUsed, resetsAt: five.resetsAt },
20 }
21}
22
23const brailleBar = (pct: number): string => {
24 const level = Math.min(Math.max(pct, 0), 100) / 100
25 let bar = ''
26 for (let i = 0; i < BAR_WIDTH; i++) {
27 const start = i / BAR_WIDTH
28 const end = (i + 1) / BAR_WIDTH
29 if (level >= end) {
30 bar += BRAILLE[7]
31 } else if (level <= start) {
32 bar += BRAILLE[0]
33 } else {
34 bar += BRAILLE[Math.min(Math.floor(((level - start) / (end - start)) * 7), 7)]
35 }
36 }
37
38 return bar
39}
40
41const levelColor = (pct: number) =>
42 pct <= 30 ? 'success' : pct <= 60 ? 'warning' : 'error'
43
44const formatHHMM = (iso: string): string | undefined => {
45 const date = new Date(iso)
46 if (Number.isNaN(date.getTime())) {
47 return undefined
48 }
49 const pad = (n: number) => String(n).padStart(2, '0')
50
51 return `${pad(date.getHours())}:${pad(date.getMinutes())}`
52}
53
54export const register: Register = on => {
55 on('session.start', async ($, e, next) => {
56 const { context, rateLimits } = await $.session.usage()
57 await update($, snapshot, () => toSnapshot(context, rateLimits))
58
59 return next(e)
60 })
61
62 on('session.measure', async ($, e, next) => {
63 await update($, snapshot, () => toSnapshot(e.context, e.rateLimits))
64
65 return next(e)
66 })
67
68 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
69 const snap = await read($, snapshot)
70 const ctx = snap?.ctxPercent
71 const five = snap?.fiveHour
72 if (e.props.hasSurvey || (ctx === undefined && five === undefined)) {
73 return next(e)
74 }
75
76 const { Box, Text } = $.ui.resolve(e)
77 const meter = (label: string, pct: number) => (
78 <Text>
79 <Text dimColor>{label} </Text>
80 <Text color={levelColor(pct)}>
81 {brailleBar(pct)} {Math.round(pct)}%
82 </Text>
83 </Text>
84 )
85 const reset = five?.resetsAt && formatHHMM(five.resetsAt)
86
87 return (
88 <Box>
89 {ctx !== undefined && meter('ctx', ctx)}
90 {ctx !== undefined && five !== undefined && <Text>{' '}</Text>}
91 {five !== undefined && meter('5h', five.percentUsed)}
92 {reset && <Text dimColor> @{reset}</Text>}
93 </Box>
94 )
95 })
96}
97types/index.d.ts 11 lines1export type UsageSnapshot = {
2 ctxPercent?: number
3 fiveHour?: { percentUsed: number; resetsAt?: string }
4}
5
6declare module 'claude-code' {
7 interface PluginState {
8 'usage-band': { snapshot: UsageSnapshot | null }
9 }
10}
11