Band showing rtk gain: tokens saved and quota preserved

Shows what rtk saved you, from rtk gain --quota.
rtk ┃ Tokens saved 566.8K ┃ Quota preserved 9.4%
| Cell | Meaning |
|---|---|
Tokens saved | Lifetime tokens saved by rtk. |
Quota preserved | Share of your subscription quota those savings represent. |
| Option | Default | Meaning |
|---|---|---|
tier | 20x | Subscription tier passed to rtk gain --tier: pro, 5x or 20x. |
pro is detected automatically from claude auth status. A Max plan cannot be told apart between 5x and 20x, so the option decides.
- until rtk gain has answered.rtk and claude on PATH.
hooks/register.tsx 89 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Gain } from '../types'
5
6const gain = atom({ plugin: 'rtk-quota', key: 'gain' } as const, null as Gain | null)
7
8export const parse = (out: string): Gain | null => {
9 const saved = /Tokens saved[^:]*:\s*(\S+)/.exec(out)?.[1]
10 const preserved = /Quota preserved:\s*(\S+)/.exec(out)?.[1]
11 return saved && preserved ? { saved, preserved } : null
12}
13
14// `claude auth status` subscriptionType -> rtk tier; max (5x or 20x) is not told apart, so the option decides
15export const tierOf = (stdout: string, fallback: string) => {
16 try {
17 return JSON.parse(stdout).subscriptionType === 'pro' ? 'pro' : fallback
18 } catch {
19 return fallback
20 }
21}
22
23// ponytail: module var resets on hot reload; costs one extra `claude auth status`
24let authOut = ''
25// ponytail: after a hot reload no session.start/turn.complete fires; one lazy fetch on first render fills the row
26let primed = false
27
28async function refresh($: EngineInterface, fallback: string) {
29 if (!authOut) {
30 const auth = await $.process.run(['claude', 'auth', 'status'], { timeoutMs: 15_000 }).catch(() => null)
31 authOut = auth?.stdout ?? ''
32 }
33 const tier = tierOf(authOut, fallback)
34 const { stdout } = await $.process.run(['rtk', 'gain', '--quota', '--tier', tier])
35 const g = parse(stdout)
36 if (g) {
37 await update($, gain, () => g)
38 $.ui.invalidate('ui.render')
39 }
40}
41
42export const register: Register = (on, options) => {
43 on('session.start', async ($, e, next) => {
44 await refresh($, String(options.tier ?? '20x')).catch(() => {})
45 return next(e)
46 })
47
48 on('turn.complete', async ($, e, next) => {
49 await refresh($, String(options.tier ?? '20x')).catch(() => {})
50 return next(e)
51 })
52
53 // stacked above the usage bar (SessionMode, bottom right)
54 on('ui.render', { component: 'SessionMode' }, async ($, e, next) => {
55 const g = await read($, gain)
56 if (!g && !primed) {
57 primed = true
58 void refresh($, String(options.tier ?? '20x')).catch(() => {})
59 }
60 const other = await next(e)
61 const { Box, Text } = $.ui.resolve(e)
62 const line = (
63 <Box>
64 <Box width={11}><Text bold color="#7dcfff">rtk</Text></Box>
65 <Box>
66 <Text color="#565f89"> ┃ </Text>
67 <Box width={24}>
68 <Text dimColor>Tokens saved </Text>
69 <Text bold color={g ? '#c0caf5' : '#565f89'}>{g?.saved ?? '-'}</Text>
70 </Box>
71 </Box>
72 <Box>
73 <Text color="#565f89"> ┃ </Text>
74 <Box>
75 <Text dimColor>Quota preserved </Text>
76 <Text bold color={g ? '#c0caf5' : '#565f89'}>{g?.preserved ?? '-'}</Text>
77 </Box>
78 </Box>
79 </Box>
80 )
81 return other.type === 'engine' ? line : (
82 <Box flexDirection="column" alignItems="flex-start">
83 {other}
84 {line}
85 </Box>
86 )
87 })
88}
89types/index.d.ts 8 lines1export type Gain = { saved: string; preserved: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'rtk-quota': { gain: Gain | null }
6 }
7}
8