Keeps the main conversation's one-hour prompt cache warm and prices it when it goes cold. A cache-reading fork 50 idle minutes after the last request, while…

Bushi's home-made Claude Code mods, as a directory marketplace named bushi-local.
cache-hugger: keeps the main conversation's one-hour prompt cache warm and prices it when it goes cold. Since 3.0.0 it does everything cache-tax 2.2.1 did, so uninstall cache-tax (it warns at session start while both are installed). Details below; it also adds a sub-agent wait rule to your CLAUDE.md, described after that.Pings. A ping is a tool-less fork of the conversation that reads the cache and so renews it, sent 50 idle minutes after the last request, then every 50 minutes. Pings run while either reason holds:
/hug-cache window: /hug-cache (6 hours), /hug-cache 90m, /hug-cache always (a 6-hour window at every session start), /hug-cache off. A paid cold write opens a 3-hour window by itself.Both reasons share one timer, so the cache is never pinged twice. A ping that finds the cache already gone, or fails, stops the pings until the next turn.
Guard. A message sent after the cache went cold (over an hour idle, context of 50k tokens or more) is refused once with its re-write price and how much of the 5-hour limit is used; sending it again pays it. /hug-cache guard warn only shows the price.
Prices. List prices from the pricing page on 17 October 2026, per model (Opus 5.5 at $8 per million for a 1-hour write and $0.20 for a read, Haiku 5.5 by prompt length). A turn counts as a cold write only when it wrote more than its context grew by, so a long turn that adds a lot of context is not mistaken for one.
Where it shows. Nothing goes to the transcript except the cold-write line. A row above the prompt (the status line where there is no such row) shows warm or cold, the time left and why (manual for /hug-cache, auto bg for background work, auto for always or after a cold write), the next ping as a countdown redrawn every minute, and the last ping's read and price. Each ping shows in a toast, and pings stopping early in a longer toast. /hug-cache status shows the model, cache state, context, cold price, break-even, background work and this session's cold writes.
Windows are kept per session in the mod's own store. Windows armed under cache-tax are not carried over.
The mod's pings only reach the main conversation: a mod can fork the main thread ($.model.fork), never a sub-agent's. A sub-agent's prompt cache lasts 5 minutes, so a sub-agent that waits longer than that on a background job re-writes its whole cache when it wakes. The workable fix is for sub-agents to wake themselves: each short wake-up reads the cache and so refreshes it.
So at every session start the mod keeps this section in ~/.claude/CLAUDE.md ($CLAUDE_CONFIG_DIR/CLAUDE.md when that is set), between <!-- KEEPWARM_BG_WAIT_RULE_START --> (the markers keep their old name so existing blocks update in place) and <!-- KEEPWARM_BG_WAIT_RULE_END -->:
Waiting in sub-agents (4-minute slices). A sub-agent that waits on a background job (a review trial, a check run, a test suite, a build, a server) waits in slices of at most 4 minutes: a Bash or Monitor wait with a timeout of at most 240 s, then a short status check, then the next slice; never one long blocking wait. A session that launches sub-agents puts the rule in every prompt for one that may wait on a background job. Waits in the main conversation are exempt: its cache lasts an hour.
CLAUDE.md; start a new one.<!-- KEEPWARM_BG_WAIT_RULE: off --> anywhere in CLAUDE.md; the mod then never adds or changes the rule.Measured on one real session on 17 October 2026: an orchestrator session (Opus 5.5, high effort) delivering one slice of a backend feature over 7.5 hours, with 19 background sub-agents, mostly Opus 5.5, that implemented and reviewed tasks and often waited 5–10 minutes on review trials and check runs. Costs below are list API prices; on a subscription the same tokens count against usage limits instead.
Cache prices ($ per million tokens, Anthropic pricing, 17 October 2026). A 5-minute write is 1.25× the input price, a 1-hour write 2×, and a read 0.1× (0.05× on Opus 5.5 and Sonnet 5.5, 0.025× on Fable 5.1). A read restarts the cache's timer at no extra cost; there are no other cache lengths.
| Model | 5-min write | 1-hour write | Read | A 5-min write costs |
|---|---|---|---|---|
| Opus 5.5 | $5 | $8 | $0.20 | 25 reads |
| Sonnet 5.5 | $2.50 | $4 | $0.10 | 25 reads |
| Fable 5.1 | $12.50 | $20 | $0.25 | 50 reads |
| Haiku 5.5, prompt ≤ 100k | $0.125 | $0.20 | $0.01 | 12.5 reads |
| Haiku 5.5, prompt > 100k | $0.625 | $1 | $0.05 | 12.5 reads |
On Opus 5.5 one avoided re-write pays for about 24 refreshes, so wake-ups win for any wait between 5 minutes and roughly an hour and a half.
What happened. The main conversation never went cold: its longest idle stretch was 59 minutes, inside its 1-hour cache. The sub-agents re-wrote their whole 5-minute cache 16 times, about 7.4 M tokens, half of everything they wrote. One sub-agent waited about 6 minutes three times and re-wrote 608k, 626k and 649k tokens.
Simulation 1: the three worst sub-agents with a refresh every 4 minutes (Opus 5.5, cache tokens only; a refresh reads the whole cached context).
| Sub-agent | As run | With 4-min refreshes | Refreshes cost | Saved |
|---|---|---|---|---|
| A (3 re-writes) | $23.08 | $14.60 | $0.53 | $8.48 |
| B (2 re-writes) | $20.27 | $14.29 | $0.43 | $5.98 |
| C (1 re-write) | $19.65 | $16.52 | $0.28 | $3.13 |
| Total | $63.00 | $45.41 | $1.24 | $17.58 (28 %) |
It took 11 refreshes. Across all 19 sub-agents: $174.87 → $141.12, saving $33.75 (19 %). The same token flow priced on other models: Sonnet 5.5 saves $8.79 of $31.50, Fable 5.1 $46.49 of $115.45.
Simulation 2: the same three sub-agents on other models (cache and output tokens; compaction leaves an assumed 12k-token summary).
| Scenario | Cost | With 4-min refreshes |
|---|---|---|
| Opus 5.5, as run | $66.40 | $46.21 |
| Sonnet 5.5, no compaction change | $33.20 | $23.11 |
Haiku 5.5, compacting at ~187k (autoCompactWindow 200000) | $3.77 (4–5 compactions each) | $3.40 |
| Haiku 5.5, compacting at ~90k | $1.00 (16–18 compactions each) | $0.96 |
Refreshes save about 30 % on Opus and Sonnet and only pennies on Haiku, whose compaction keeps contexts small. Haiku's price rises 5× above 100k-token prompts, so compacting below 100k is 3–4× cheaper than at 200k, at the price of many more lossy summaries.
Why not the 1-hour cache instead? The 1-hour cache costs 60 % more on every write, and the choice turns on how much a conversation writes compared with how long it sits idle:
Claude Code's split, the 1-hour cache for the main conversation and the 5-minute cache for sub-agents, fits this kind of work; the rule covers the one gap, sub-agent waits over 5 minutes.
Method and limits. The simulations replay each request's cache read and write counts from the session transcripts. A gap of more than 5 minutes between two of a sub-agent's requests counts as a re-write of the whole context; with refreshes, a refresh every 4 minutes of a gap reads the whole context. The model-swap rows assume every model would have used the same tokens and turns as Opus did, which won't hold exactly. A 4-minute wake-up costs a little more than a bare refresh, since it also sends a status check and its reply. One session's numbers, not a general benchmark.
Clone into ~/.claude/local-marketplace, then in Claude Code:
/plugin marketplace add ~/.claude/local-marketplace
/plugin install cache-hugger@bushi-local
The marketplace is read in place, so after a git pull run /reload-plugins or start a new session.
From a plugin's folder: claude plugin test .
hooks/register.ts 711 lines1import type { EngineInterface, Register, SessionRateLimit } from 'claude-code'
2import { statusIcons } from './status-icons'
3
4// Keeps the main conversation's one-hour prompt cache warm and prices it when
5// it goes cold. Pings are cache-reading forks of the conversation (`$.model.fork`)
6// sent 50 idle minutes after the last request, so the next request reads the
7// cache instead of re-writing it. They run while either reason holds:
8//
9// - background work: a turn ended with a shell, subagent, monitor or workflow
10// still in flight, which will wake the session later. Pings then run until a
11// turn leaves no work, for at most 12 hours after the last turn.
12// - a /hug-cache window: a time the user picked (6 hours by default, `always` for
13// every session), or 3 hours after a cold write was paid.
14//
15// A message sent on a cold cache is refused once with its re-write price (or
16// only warned about, with /hug-cache guard warn). Nothing goes to the
17// transcript: the state shows in a row above the prompt (the status line where
18// there is no such row), each ping in a toast, and pings stopping early in a
19// longer toast.
20//
21// A mod can't ping a subagent's cache (`$.model.fork` forks the main thread
22// only), and a subagent's cache lasts five minutes. So at session start the mod
23// also keeps a rule in the user's CLAUDE.md that tells subagents to wait in
24// slices of at most four minutes; each wake-up then refreshes their cache.
25//
26// The window, guard, pricing and indicator are ported from cache-tax 2.2.1 by
27// Karan Bansal (MIT); see NOTICE.
28
29const TTL_MS = 60 * 60 * 1000
30export const PING_AFTER_MS = 50 * 60 * 1000
31/** Pings for background work stop this long after the last turn ended, in case a job hangs. */
32export const CAP_MS = 12 * 60 * 60 * 1000
33const MIN_PING_MS = 60 * 1000
34/** How often the countdown is redrawn while pings are scheduled. */
35const TICK_MS = 60 * 1000
36const AUTO_WARM_MS = 3 * 60 * 60 * 1000
37const DEFAULT_WINDOW_MS = 6 * 60 * 60 * 1000
38const BIG_TOKENS = 50000
39export const PING_PROMPT = 'Reply with the single word: warm'
40const KEY_DEADLINE = 'deadline'
41const KEY_EVERY = 'every'
42const KEY_GUARD = 'guard'
43const KEY_ALWAYS = 'always'
44
45export const RULE_START = '<!-- KEEPWARM_BG_WAIT_RULE_START -->'
46export const RULE_END = '<!-- KEEPWARM_BG_WAIT_RULE_END -->'
47/** Anywhere in CLAUDE.md, keeps the mod from adding or updating the rule. */
48export const RULE_OFF = '<!-- KEEPWARM_BG_WAIT_RULE: off -->'
49const RULE_HEADING = '## Waiting in sub-agents (4-minute slices)'
50export const WAIT_RULE = `${RULE_START}
51${RULE_HEADING}
52
53A sub-agent's prompt cache lasts 5 minutes; one idle tool call longer than that makes it rewrite its whole cache when it wakes (measured 17 Oct 2026: 600–700k-token caches rewritten 16 times in one session; see the cache-hugger README). So:
54
55- **If you are a sub-agent** and wait on a background job (a review trial, a check run, a test suite, a build, a server), wait in slices of at most 4 minutes: a Bash or Monitor wait with a timeout of at most 240 s, then a short status check, then the next slice. Never make one long blocking wait.
56- **If you launch sub-agents** (Agent tool, workflows), put this rule in every prompt for one that may wait on a background job.
57- Waits in the main conversation are exempt: its cache lasts an hour.
58${RULE_END}`
59
60/**
61 * CLAUDE.md's text with the wait rule in it, or null when it needs no change:
62 * the marked block is replaced when it differs, an unmarked section under the
63 * rule's heading (added by hand) is taken over, and otherwise the rule is
64 * appended. RULE_OFF anywhere leaves the file alone.
65 */
66export function withWaitRule(text: string): string | null {
67 if (text.includes(RULE_OFF)) return null
68 const start = text.indexOf(RULE_START)
69 const end = text.indexOf(RULE_END, start)
70 if (start >= 0 && end > start) {
71 const next = text.slice(0, start) + WAIT_RULE + text.slice(end + RULE_END.length)
72 return next === text ? null : next
73 }
74 const heading = text.search(/^## Waiting in sub-agents \(4-minute slices\)[ \t]*$/m)
75 if (heading >= 0) {
76 const rest = text.slice(heading + RULE_HEADING.length)
77 const after = rest.search(/^(## |<!-- )/m)
78 const tail = after < 0 ? '\n' : '\n\n' + rest.slice(after)
79 return text.slice(0, heading) + WAIT_RULE + tail
80 }
81 const body = text.trimEnd()
82 return (body ? body + '\n\n' : '') + WAIT_RULE + '\n'
83}
84
85async function ensureWaitRule($: EngineInterface) {
86 try {
87 const config = (await $.env.get('CLAUDE_CONFIG_DIR')) ?? `${await $.env.get('HOME')}/.claude`
88 const path = `${config}/CLAUDE.md`
89 const text = (await $.fs.exists(path)) ? await $.fs.read(path) : ''
90 const next = withWaitRule(text)
91 if (next !== null) await $.fs.write(path, next)
92 } catch (err) {
93 $.ui.log(`cache-hugger: could not update CLAUDE.md with the sub-agent wait rule: ${err instanceof Error ? err.message : String(err)}`, { to: 'debug' })
94 }
95}
96
97/** $ per million tokens: cache read, 1-hour cache write, output. Uncached input is half the 1-hour write. */
98export type Price = [number, number, number]
99
100// List prices from https://platform.claude.com/docs/en/about-claude/pricing, 17 October 2026.
101// A model id matches the first row whose family it contains, so longer names come first.
102// The second price applies to prompts over 100,000 tokens (Haiku 5.5 only).
103const PRICES: Array<[string, Price, Price?]> = [
104 ['fable-5-1', [0.25, 20, 50]],
105 ['mythos-5-1', [0.25, 20, 50]],
106 ['fable-5', [1, 20, 50]],
107 ['mythos-5', [1, 20, 50]],
108 ['opus-5-5', [0.2, 8, 20]],
109 ['opus-5', [0.5, 10, 25]],
110 ['opus-4-1', [1.5, 30, 75]],
111 ['opus-4-5', [0.5, 10, 25]],
112 ['opus-4-6', [0.5, 10, 25]],
113 ['opus-4-7', [0.5, 10, 25]],
114 ['opus-4-8', [0.5, 10, 25]],
115 ['opus-4', [1.5, 30, 75]],
116 ['sonnet-5-5', [0.1, 4, 10]],
117 ['sonnet-5', [0.2, 4, 10]],
118 ['sonnet', [0.3, 6, 15]],
119 ['haiku-5-5', [0.01, 0.2, 0.5], [0.05, 1, 2.5]],
120 ['haiku-3-5', [0.08, 1.6, 4]],
121 ['haiku', [0.1, 2, 5]],
122]
123const TIER_TOKENS = 100000
124
125/** The model's prices for a request whose prompt (cache read + write + input) is `prompt` tokens; null for a model not in the table. */
126export function priceOf(model: string | null, prompt = 0): Price | null {
127 const m = (model ?? '').toLowerCase().replace(/[\s.]+/g, '-')
128 for (const [family, base, above] of PRICES) if (m.includes(family)) return above && prompt > TIER_TOKENS ? above : base
129 return null
130}
131
132type PingRecord = { at: number; read: number; write: number; usd: number | null; warm: boolean }
133type Miss = { at: number; tokens: number; usd: number | null }
134type GuardMode = 'refuse' | 'warn'
135type Summary = { id: string; type: string; status: string }
136type Usage = { input_tokens: number; output_tokens: number; cache_read_input_tokens: number; cache_creation_input_tokens: number }
137
138export type State = {
139 hasBand: boolean
140 sid: string
141 /** When the /hug-cache window ends; 0 when none is open. */
142 deadline: number
143 /** When pings for background work stop (12 h after the turn that left it); 0 when the last turn left none. */
144 bgUntil: number
145 every: number
146 /** The window was opened by the mod (always, or after a cold write), not by /hug-cache. */
147 auto: boolean
148 always: boolean
149 lastRequestAt: number
150 lastModel: string | null
151 ctx: number
152 compacted: boolean
153 guard: GuardMode
154 ackedAt: number
155 coldWritePending: boolean
156 misses: Miss[]
157 pending: { cancel: () => void } | null
158 /** Redraws the countdown while pings are scheduled; nothing else changes it between turns. */
159 tick: { cancel: () => void } | null
160 last: PingRecord | null
161 stopped: string | null
162}
163
164export function parseDuration(text: string): number | null {
165 const m = /^(?:(\d+)h)?(?:(\d+)m)?$/.exec(text.trim())
166 if (!m || (m[1] === undefined && m[2] === undefined)) return null
167 return (Number(m[1] ?? 0) * 60 + Number(m[2] ?? 0)) * 60 * 1000
168}
169
170export function fmtDuration(ms: number): string {
171 const total = Math.max(0, Math.round(ms / 60000))
172 const h = Math.floor(total / 60)
173 const m = total % 60
174 if (h >= 48) return `${Math.floor(h / 24)}d ${h % 24}h`
175 return h > 0 ? `${h}h${String(m).padStart(2, '0')}m` : `${m}m`
176}
177
178function fmtUsd(usd: number | null): string {
179 return usd == null ? 'n/a' : '$' + (usd >= 100 ? usd.toFixed(0) : usd.toFixed(2))
180}
181
182export function fmtTokens(n: number): string {
183 return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1000 ? Math.round(n / 1000) + 'k' : String(n)
184}
185
186/** `HH:MM` on the host's clock. */
187export function clockTime(ms: number): string {
188 const d = new Date(ms)
189 return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
190}
191
192function coldUsd(s: State): number | null {
193 const price = priceOf(s.lastModel, s.ctx)
194 return price ? s.ctx * price[1] / 1e6 : null
195}
196
197function warmUsd(s: State): number | null {
198 const price = priceOf(s.lastModel, s.ctx)
199 return price ? s.ctx * price[0] / 1e6 : null
200}
201
202/** Everything a fork bills: the cache read, any cache write, uncached input at the base rate (half the 1h write rate), and the output. */
203function pingUsd(u: Usage, price: Price): number {
204 return (u.cache_read_input_tokens * price[0] + u.cache_creation_input_tokens * price[1] + u.input_tokens * price[1] / 2 + u.output_tokens * price[2]) / 1e6
205}
206
207/** A warm ping reads the prefix and writes only its own message; a write of a tenth of the read or more means the prefix broke. */
208export function isWarm(u: Pick<Usage, 'cache_read_input_tokens' | 'cache_creation_input_tokens'>): boolean {
209 return u.cache_read_input_tokens > 0 && u.cache_creation_input_tokens < 0.1 * u.cache_read_input_tokens
210}
211
212/** The read-only upper bound: pings at the cache-read rate that cost as much as one cold write of the same context. */
213function breakEvenPings(s: State): number | null {
214 const price = priceOf(s.lastModel, s.ctx)
215 if (!price || s.ctx <= 0) return null
216 return Math.floor(price[1] / price[0])
217}
218
219function isCold(s: State, now: number): boolean {
220 return s.lastRequestAt > 0 && !s.compacted && now - s.lastRequestAt >= TTL_MS
221}
222
223/** When pings stop: the later of the window's end and background work's cap; 0 when neither holds. */
224function endOf(s: State): number {
225 return Math.max(s.deadline, s.bgUntil)
226}
227
228/** The 5-hour window's use, as the last API response reported it; null off a subscription. */
229async function fiveHour($: EngineInterface): Promise<SessionRateLimit | null> {
230 try {
231 return (await $.session.usage()).rateLimits.find(r => r.kind === 'five_hour') ?? null
232 } catch {
233 return null
234 }
235}
236
237/** The cold-cache notice: a heading, then the price and the 5-hour limit as aligned key-value rows. */
238export function guardText(s: State, now: number, limit: SessionRateLimit | null): string {
239 const price = priceOf(s.lastModel, s.ctx)
240 const rate = price ? `$${price[1]}/MTok` : 'write rate'
241 const warm = warmUsd(s)
242 const reset = limit?.resetsAt ? Date.parse(limit.resetsAt) : NaN
243 return [
244 `Cache cold for ${fmtDuration(now - s.lastRequestAt - TTL_MS)}`,
245 ` re-write ${fmtUsd(coldUsd(s))} (${fmtTokens(s.ctx)} tok × ${rate}${warm == null ? '' : `; warm turn ${fmtUsd(warm)}`})`,
246 ` 5h limit ${limit ? `${limit.percentUsed}% used${Number.isNaN(reset) ? '' : `, resets ${clockTime(reset)}`}` : 'n/a'}`,
247 ].join('\n')
248}
249
250export type ResumeFields = {
251 source: string
252 model?: string
253 context_tokens?: number
254 seconds_since_last_response?: number
255 prompt_cache_likely_expired?: boolean
256 estimated_cache_write_usd?: number
257}
258
259/** /clear starts a new conversation in the same process; nothing priced before it still exists. */
260export function resetForClear(s: State) {
261 s.ctx = 0
262 s.lastRequestAt = 0
263 s.compacted = false
264 s.ackedAt = 0
265 s.coldWritePending = false
266 s.misses = []
267 s.bgUntil = 0
268 disarm(s)
269}
270
271/** Applies a resumed session's fields to the state; returns the line to log, if any. */
272export function seedFromResume(s: State, e: ResumeFields, now: number): string | null {
273 if (e.source !== 'resume' && e.source !== 'fork') return null
274 if (typeof e.context_tokens === 'number' && e.context_tokens > 0) s.ctx = e.context_tokens
275 if (typeof e.seconds_since_last_response === 'number') s.lastRequestAt = now - e.seconds_since_last_response * 1000
276 if (typeof e.model === 'string') s.lastModel = e.model
277 s.compacted = false
278 if (e.prompt_cache_likely_expired !== true || s.ctx < BIG_TOKENS) return null
279 const usd = typeof e.estimated_cache_write_usd === 'number' ? fmtUsd(e.estimated_cache_write_usd) : fmtUsd(coldUsd(s))
280 return `resuming cold. The first message re-writes ${s.ctx.toLocaleString('en-US')} tokens, about ${usd}. /clear and paste a summary if you only need the conclusions.`
281}
282
283function statusText(s: State, now: number): string | undefined {
284 if (s.stopped) return `pings stopped: ${s.stopped}`
285 const end = endOf(s)
286 if (!end) return undefined
287 const why = s.bgUntil > s.deadline ? 'auto bg' : s.auto ? 'auto' : 'manual'
288 const pingText = s.last ? ` · last ping read ${fmtTokens(s.last.read)} ${fmtUsd(s.last.usd)}` : ''
289 const nextText = !s.lastRequestAt ? ' · waiting for the first turn'
290 : s.compacted ? ' · waiting for the first turn after compaction'
291 : isCold(s, now) ? ` · cold now, first ping ${fmtDuration(s.every)} after the next turn`
292 : ` · ping in ${fmtDuration(s.lastRequestAt + s.every - now)}`
293 return `${fmtDuration(end - now)} left (${why})${nextText}${pingText}`
294}
295
296function updateStatus($: EngineInterface, s: State, now: number) {
297 if (s.hasBand) $.ui.invalidate('ui.render')
298 else $.ui.status(statusText(s, now))
299 syncTick($, s)
300}
301
302/** Keeps a one-minute redraw running exactly while pings are scheduled, so "left" and "ping in" count down. */
303function syncTick($: EngineInterface, s: State) {
304 if (endOf(s) && !s.tick) {
305 s.tick = $.clock.every(TICK_MS, () => {
306 void (async () => {
307 const now = await $.clock.now()
308 if (s.hasBand) $.ui.invalidate('ui.render')
309 else $.ui.status(statusText(s, now))
310 })()
311 })
312 } else if (!endOf(s) && s.tick) {
313 s.tick.cancel()
314 s.tick = null
315 }
316}
317
318function disarm(s: State) {
319 if (s.pending) s.pending.cancel()
320 s.pending = null
321}
322
323// The window and its ping period belong to the session that armed them, so a
324// second session, or one resumed from another transcript, never inherits them
325// and cannot turn them off. The always switch and the guard mode stay global.
326function deadlineKey(s: State): string {
327 return `${KEY_DEADLINE}:${s.sid}`
328}
329
330function everyKey(s: State): string {
331 return `${KEY_EVERY}:${s.sid}`
332}
333
334/** Clears this session's own dead window and bare keys from an old store. Other sessions' keys are never touched: a read followed by a delete cannot be made atomic against their renewal. */
335async function prune($: EngineInterface, s: State, now: number) {
336 for (const key of [KEY_DEADLINE, deadlineKey(s)]) {
337 const deadline = await $.store.get(key)
338 if (deadline === undefined) continue
339 if (typeof deadline === 'number' && deadline > now) continue
340 await $.store.delete(key)
341 await $.store.delete(KEY_EVERY + key.slice(KEY_DEADLINE.length))
342 }
343}
344
345/** Pings have stopped early: the whole reason in a long toast; the row keeps the short form. */
346function notifyStopped($: EngineInterface, now: number, why: string) {
347 $.ui.toast(`Pings stopped at ${clockTime(now)}: ${why}. A request after an hour idle will re-write the cache.`, { timeoutMs: 15000 })
348}
349
350async function stop($: EngineInterface, s: State, why: string | null, forgetAlways = false) {
351 s.deadline = 0
352 s.bgUntil = 0
353 s.every = PING_AFTER_MS
354 s.stopped = why
355 disarm(s)
356 await $.store.delete(deadlineKey(s))
357 await $.store.delete(everyKey(s))
358 if (forgetAlways) {
359 s.always = false
360 await $.store.delete(KEY_ALWAYS)
361 }
362 const now = await $.clock.now()
363 updateStatus($, s, now)
364 if (why) notifyStopped($, now, why)
365}
366
367async function arm($: EngineInterface, s: State) {
368 disarm(s)
369 const end = endOf(s)
370 if (!end) return
371 const now = await $.clock.now()
372 if (now >= end) {
373 // A window ends quietly; background work may still be running when its cap ends.
374 const capped = s.bgUntil > 0 && s.bgUntil >= s.deadline
375 return stop($, s, capped ? '12 h cap after the last turn, background work may still be running' : null)
376 }
377 // A cold cache still needs expiry cleanup, but must not send a model request.
378 if (s.lastRequestAt && !s.compacted && !isCold(s, now)) {
379 const untilCold = s.lastRequestAt + TTL_MS - now
380 const delay = Math.min(end - now, untilCold, Math.max(1000, s.lastRequestAt + s.every - now))
381 s.pending = $.clock.after(delay, () => { void ping($, s) })
382 } else {
383 s.pending = $.clock.after(end - now, () => { void arm($, s) })
384 }
385 updateStatus($, s, now)
386}
387
388async function ping($: EngineInterface, s: State) {
389 s.pending = null
390 const end = endOf(s)
391 if (!end) return
392 const now = await $.clock.now()
393 if (now >= end) return arm($, s)
394 // A turn in the meantime re-armed the timer; this callback is stale.
395 if (isCold(s, now)) return arm($, s)
396 if (now - s.lastRequestAt < s.every - 1000) return
397 const sent = s.lastRequestAt
398 let reply
399 try {
400 reply = await $.model.fork({ prompt: PING_PROMPT })
401 } catch (err) {
402 return stop($, s, `the ping failed, ${err instanceof Error ? err.message : String(err)}`)
403 }
404 // A turn while the ping was out stamped a newer request and re-armed; it decides what comes next.
405 if (s.lastRequestAt !== sent) return
406 // Older Claude Code releases returned null instead of a failure object.
407 if (reply === null) return stop($, s, 'the engine did not send the ping, either the snapshot was cold or the API call failed')
408 if (reply.isAnswered === false) {
409 const reason = reply.reason === 'nothing-to-fork' ? 'no conversation to warm yet'
410 : reply.reason === 'api-error' ? `the API call failed${reply.status === null ? '' : ` (${reply.status})`}`
411 : reply.reason === 'aborted' ? 'the ping was interrupted'
412 : 'the ping returned no text'
413 return stop($, s, reason)
414 }
415 const u = reply.usage
416 const price = priceOf(s.lastModel, u.cache_read_input_tokens + u.cache_creation_input_tokens + u.input_tokens)
417 const warm = isWarm(u)
418 const usd = price ? pingUsd(u, price) : null
419 s.last = { at: now, read: u.cache_read_input_tokens, write: u.cache_creation_input_tokens, usd, warm }
420 if (!warm) return stop($, s, `the ping read ${fmtTokens(u.cache_read_input_tokens)} and wrote ${fmtTokens(u.cache_creation_input_tokens)} tokens (${fmtUsd(usd)}), the cache was already gone`)
421 s.lastRequestAt = now
422 $.ui.toast(`Cache refreshed: the ping read ${fmtTokens(u.cache_read_input_tokens)} tokens (${fmtUsd(usd)}).`)
423 await arm($, s)
424}
425
426/** The reply to an arming command; on a cold cache it says when the first ping can come. */
427function armedText(s: State, now: number, windowMs: number): string {
428 if (isCold(s, now)) return `Hugging the cache to keep it warm for ${fmtDuration(windowMs)}. It is cold now, so the first ping comes ${fmtDuration(s.every)} after the next turn`
429 return `Hugging the cache to keep it warm for ${fmtDuration(windowMs)}: a ping ${fmtDuration(s.every)} into each idle stretch reads it instead of re-writing it`
430}
431
432async function startWindow($: EngineInterface, s: State, windowMs: number, every: number, auto = false) {
433 const now = await $.clock.now()
434 s.every = every
435 s.auto = auto
436 if (every === PING_AFTER_MS) await $.store.delete(everyKey(s))
437 else await $.store.set(everyKey(s), every)
438 s.deadline = now + windowMs
439 s.stopped = null
440 await $.store.set(deadlineKey(s), s.deadline)
441 await arm($, s)
442}
443
444function card(s: State, now: number): string {
445 const lines: string[] = []
446 lines.push(`${s.lastModel ?? 'model not seen yet'}`)
447 if (s.compacted) lines.push('state reset by compaction, waiting for the first turn')
448 else if (!s.lastRequestAt) lines.push('state no request yet this session')
449 else if (isCold(s, now)) lines.push(`state COLD, last request ${fmtDuration(now - s.lastRequestAt)} ago`)
450 else lines.push(`state warm, ${fmtDuration(s.lastRequestAt + TTL_MS - now)} left`)
451 lines.push(`context ${s.ctx.toLocaleString('en-US')} tokens`)
452 lines.push(`cold cost ${fmtUsd(coldUsd(s))} to re-write it (warm turn ${fmtUsd(warmUsd(s))})`)
453 const always = s.always ? ' (always)' : ''
454 const idle = s.always ? 'off until the next session start, which arms 6h00m (always)' : 'off (/hug-cache to arm it for 6h00m)'
455 lines.push(`hugging ${endOf(s) ? 'on, ' + (statusText(s, now) ?? '') + always : s.stopped ? `stopped, ${s.stopped}${always}` : idle}`)
456 lines.push(`background ${s.bgUntil ? `work in flight, pings until ${clockTime(s.bgUntil)} at the latest` : 'no work in flight at the last turn'}`)
457 const pings = breakEvenPings(s)
458 if (pings != null) lines.push(`break-even up to ${pings} pings at the read rate cost one cold write, about ${fmtDuration(pings * s.every)} of idle at one ping per ${fmtDuration(s.every)}`)
459 lines.push(`guard ${s.guard === 'refuse' ? 'refuse once (/hug-cache guard warn to only show the price)' : 'warn only (/hug-cache guard refuse to be stopped once)'}`)
460 const paid = s.misses.reduce((a, m) => a + (m.usd ?? 0), 0)
461 lines.push(`session ${s.misses.length} cold write${s.misses.length === 1 ? '' : 's'} paid, ${fmtUsd(paid)}`)
462 return lines.join('\n')
463}
464
465export function freshState(): State {
466 return {
467 hasBand: false, sid: '', deadline: 0, bgUntil: 0, every: PING_AFTER_MS, auto: false, always: false, lastRequestAt: 0, lastModel: null, ctx: 0, compacted: false,
468 guard: 'refuse', ackedAt: 0, coldWritePending: false, misses: [], pending: null, tick: null, last: null, stopped: null,
469 }
470}
471
472export const register: Register = on => {
473 const s = freshState()
474 let light = false
475 let noColor = false
476
477 on('config.set', { key: 'theme' }, async ($, e, next) => {
478 const result = await next(e)
479 if (result.deny === undefined) {
480 light = typeof result.value === 'string' && result.value.startsWith('light')
481 if (s.hasBand) $.ui.invalidate('ui.render')
482 }
483 return result
484 })
485
486 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
487 const rest = await next(e)
488 if (!s.hasBand || e.props.hasSurvey) return rest
489 const now = await $.clock.now()
490 const text = statusText(s, now)
491 if (!text || (endOf(s) && now >= endOf(s))) return rest
492 const state = s.stopped ? 'stopped' : !s.lastRequestAt || s.compacted ? 'unknown' : isCold(s, now) ? 'cold' : 'warm'
493 const color = noColor ? undefined : state === 'warm' ? 'success' : state === 'cold' ? (light ? '#c15f3c' : '#d97757') : undefined
494 const { Box, Text } = $.ui.resolve(e)
495 const asset = statusIcons[light ? 'light' : 'dark'][state === 'warm' || state === 'cold' ? state : 'neutral']
496 const icon = noColor ? Text({ bold: true, children: ['[>]'] })
497 : e.surface === 'terminal' ? $.ui.resolve(e).Image({ source: { png: asset.png }, columns: 3, rows: 1, alt: '[>]' })
498 : e.surface === 'desktop' ? $.ui.resolve(e).Svg({ source: asset.svg, width: 18, height: 18, alt: 'cache-hugger' })
499 : Text({ color, bold: true, children: ['[>]'] })
500 return Box({ flexDirection: 'column', children: [
501 rest,
502 Box({ flexDirection: 'row', alignItems: 'center', children: [
503 Box({ flexShrink: 0, children: [icon] }),
504 Text({ children: [
505 ' ',
506 Text({ color, bold: true, children: [state] }),
507 ` · ${text}`,
508 ] }),
509 ] }),
510 ] })
511 })
512
513 on('session.start', async ($, e, next) => {
514 const r = await next(e)
515 s.hasBand = e.surface === 'terminal' || e.surface === 'desktop'
516 if (s.hasBand) {
517 const theme = (await $.config.list()).find(row => row.key === 'theme')?.value
518 light = typeof theme === 'string' && theme.startsWith('light')
519 noColor = Boolean(await $.env.get('NO_COLOR'))
520 $.ui.status(undefined)
521 }
522 await ensureWaitRule($)
523 s.sid = await $.session.id()
524 const now = await $.clock.now()
525 await prune($, s, now)
526 const saved = await $.store.get(deadlineKey(s))
527 const savedEvery = await $.store.get(everyKey(s))
528 const savedGuard = await $.store.get(KEY_GUARD)
529 s.deadline = typeof saved === 'number' && saved > now ? saved : 0
530 s.every = typeof savedEvery === 'number' && savedEvery >= MIN_PING_MS ? savedEvery : PING_AFTER_MS
531 s.guard = savedGuard === 'warn' ? 'warn' : 'refuse'
532 s.always = (await $.store.get(KEY_ALWAYS)) === true
533 // Always means a fresh default window every session, whatever the last one left behind.
534 if (s.always) await startWindow($, s, DEFAULT_WINDOW_MS, PING_AFTER_MS, true)
535 const usage = await $.session.usage()
536 if (usage.context.tokens) s.ctx = usage.context.tokens
537 await $.command.register({
538 name: 'hug-cache',
539 description: 'Hug the prompt cache to keep it warm: bare for 6h, a window such as 90m, always, off, status (cache state and cold price), or guard warn|refuse (cache-hugger)',
540 argumentHint: '[6h | always | off | status | guard warn | guard refuse]',
541 immediate: true,
542 })
543 // cache-tax, which this mod replaces, would ping and guard a second time.
544 const commands = await $.command.list()
545 if (commands.some(c => c.name === 'cache-tax' || c.name === 'cache-tax:status')) {
546 $.ui.log('cache-tax is also installed, so the cache is pinged and a cold send is guarded twice. cache-hugger does all of its work; uninstall cache-tax.')
547 }
548 updateStatus($, s, now)
549 return r
550 })
551
552 // Resume fields seed the guard before any turn of the resumed session has run.
553 on('classic.SessionStart', async ($, e, next) => {
554 const r = await next(e)
555 if (e.source === 'clear') {
556 await stop($, s, null)
557 s.stopped = null
558 resetForClear(s)
559 s.sid = await $.session.id()
560 updateStatus($, s, await $.clock.now())
561 return r
562 }
563 const line = seedFromResume(s, e, await $.clock.now())
564 // The resume payload may omit the model; without it the guard cannot price the cold write.
565 if (!s.lastModel) s.lastModel = await $.session.model()
566 if (line) $.ui.log(line)
567 // The seeded clock decides whether a restored or always window pings before the first turn: never when it is cold.
568 await arm($, s)
569 return r
570 })
571
572 on('command.run', { command: 'hug-cache' }, async ($, e) => {
573 const words = String(e.args ?? '').trim().split(/\s+/).filter(Boolean)
574 const now = await $.clock.now()
575 if (words[0] === 'off') {
576 const wasAlways = s.always
577 await stop($, s, null, true)
578 return { text: wasAlways ? 'Stopped hugging the cache, and no longer starting at session start' : 'Stopped hugging the cache' }
579 }
580 if (words[0] === 'status') return { text: card(s, now) }
581 if (words[0] === 'guard') {
582 if (words[1] !== 'warn' && words[1] !== 'refuse') return { text: '/hug-cache guard takes warn or refuse' }
583 s.guard = words[1]
584 await $.store.set(KEY_GUARD, s.guard)
585 return { text: s.guard === 'refuse' ? 'guard set to refuse once: a cold send is dropped with its price, the resend goes through' : 'guard set to warn: a cold send goes through with its price shown' }
586 }
587 if (words[0] === 'always') {
588 s.always = true
589 await $.store.set(KEY_ALWAYS, true)
590 await startWindow($, s, DEFAULT_WINDOW_MS, PING_AFTER_MS)
591 const cold = isCold(s, now) ? `. The cache is cold now, so the first ping comes ${fmtDuration(s.every)} after the next turn` : ''
592 return { text: `Hugging the cache to keep it warm, always: every session starts with a ${fmtDuration(DEFAULT_WINDOW_MS)} hug; /hug-cache off turns it off for good${cold}` }
593 }
594 if (!words.length) {
595 await startWindow($, s, DEFAULT_WINDOW_MS, PING_AFTER_MS)
596 return { text: armedText(s, now, DEFAULT_WINDOW_MS) }
597 }
598 const window = parseDuration(words[0])
599 if (window == null) return { text: '/hug-cache takes a window such as 6h or 90m, or always, off, status, or guard warn|refuse' }
600 // "every 2m" is a testing knob and lasts only for the window it was given with.
601 let every = PING_AFTER_MS
602 if (words[1] === 'every') {
603 const period = parseDuration(words[2] ?? '')
604 if (period == null || period < MIN_PING_MS) return { text: 'every takes a period of at least 1m' }
605 every = period
606 }
607 await startWindow($, s, window, every)
608 return { text: armedText(s, now, window) }
609 })
610
611 // The message that pays. Only its first character is read.
612 on('prompt.submit', async ($, e, next) => {
613 if (e.origin.kind === 'plugin') return next(e)
614 if (typeof e.text !== 'string' || e.text.trimStart().startsWith('/')) return next(e)
615 const now = await $.clock.now()
616 if (!isCold(s, now) || s.ctx < BIG_TOKENS) return next(e)
617 const limit = await fiveHour($)
618 if (s.guard === 'warn') {
619 $.ui.log(`${guardText(s, now, limit)}\nSending anyway; the cache is then hugged for ${fmtDuration(AUTO_WARM_MS)}.`)
620 s.coldWritePending = true
621 return next(e)
622 }
623 if (s.ackedAt === s.lastRequestAt) {
624 s.ackedAt = 0
625 s.coldWritePending = true
626 return next(e)
627 }
628 s.ackedAt = s.lastRequestAt
629 return { drop: `${guardText(s, now, limit)}\nSend again to pay it (then hugged ${fmtDuration(AUTO_WARM_MS)}), or /clear and start from a note.` }
630 })
631
632 on('turn.step', async function* ($, e, next) {
633 if (!e.agentId) s.lastRequestAt = await $.clock.now()
634 yield* next(e)
635 })
636
637 // Stop fires when the main conversation's turn ends; background_tasks lists the
638 // work still in flight that will wake it later. turn.complete acts on what
639 // Stop saw, once the turn is over.
640 let seen: readonly Summary[] | undefined
641 on('classic.Stop', async ($, e, next) => {
642 const r = await next(e)
643 seen = e.background_tasks ?? []
644 return r
645 })
646
647 on('turn.complete', async ($, e, next) => {
648 const r = await next(e)
649 if (e.agentId) return r
650 const now = await $.clock.now()
651 // A sleeping host may deliver this turn before the expired window's timer.
652 if (s.deadline && now >= s.deadline) {
653 s.deadline = 0
654 s.every = PING_AFTER_MS
655 await $.store.delete(deadlineKey(s))
656 await $.store.delete(everyKey(s))
657 }
658 // turn.step stamps the exact request time; when no step of this turn did, the turn's end is the floor.
659 if (now - s.lastRequestAt > e.durationMs) s.lastRequestAt = now
660 s.compacted = false
661 s.ackedAt = 0
662 const u = e.usage
663 if (u) {
664 if (u.model) s.lastModel = u.model
665 const prev = s.ctx
666 // A turn's usage is its responses summed, so a ten-step turn reports ten
667 // reads of the context. The live window is the engine's figure; the sum
668 // is only the fallback for a host that reports no tokens.
669 const write = u.cache_creation_input_tokens
670 const live = (await $.session.usage()).context.tokens
671 s.ctx = live && live > 0 ? live : u.input_tokens + u.cache_read_input_tokens + write
672 // Each warm step writes only what it added, so a long turn's summed write
673 // can match the context's growth without anything going cold. Only the
674 // write beyond that growth re-wrote the prefix the turn started from.
675 const rewrote = Math.max(0, write - Math.max(0, s.ctx - prev))
676 const full = prev > 20000 && rewrote >= 0.5 * prev
677 if (full || s.coldWritePending) {
678 const price = priceOf(s.lastModel, s.ctx)
679 const usd = price ? rewrote * price[1] / 1e6 : null
680 s.misses.push({ at: now, tokens: rewrote, usd })
681 if (s.deadline < now + AUTO_WARM_MS) {
682 await startWindow($, s, AUTO_WARM_MS, s.every, true)
683 $.ui.log(`cold write of ${fmtTokens(rewrote)} tokens paid (${fmtUsd(usd)}). Keeping the cache warm for ${fmtDuration(AUTO_WARM_MS)} so it is not paid again today; /hug-cache off to stop.`)
684 }
685 }
686 }
687 s.coldWritePending = false
688 if (seen !== undefined) {
689 s.bgUntil = seen.length ? now + CAP_MS : 0
690 if (seen.length) s.stopped = null
691 seen = undefined
692 }
693 // arm draws nothing when no reason is left, so the row is cleared here.
694 if (!endOf(s)) updateStatus($, s, now)
695 await arm($, s)
696 return r
697 })
698
699 on('session.compact', async ($, e, next) => {
700 const r = await next(e)
701 if (!e.agentId) {
702 s.compacted = true
703 s.ctx = 0
704 s.ackedAt = 0
705 disarm(s)
706 await arm($, s)
707 }
708 return r
709 })
710}
711hooks/status-icons.ts 32 lines1// Generated by tools/generate-status-icons.py from the official SVG assets.
2export const statusIcons = {
3 "dark": {
4 "warm": {
5 "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <title>Cache Tax</title>\n <g stroke=\"#faf9f5\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path fill=\"#78b85a\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n <path d=\"m40 32 8 5-8 5\"/>\n </g>\n</svg>\n",
6 "png": "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"
7 },
8 "cold": {
9 "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <title>Cache Tax</title>\n <g stroke=\"#faf9f5\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path fill=\"#d97757\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n <path d=\"m40 32 8 5-8 5\"/>\n </g>\n</svg>\n",
10 "png": "iVBORw0KGgoAAAANSUhEUgAAAGAAAABACAYAAADlNHIOAAAABmJLR0QA/wD/AP+gvaeTAAAMwklEQVR4nO1ce3RUxR3+ZjePFZKQ3Zm7G5KIJg1BsbaFBkSKoDFBqohiTSPWA/FRxHCowQePEF8VQkRIBTynkacgRwEh1pZDFUIERVDURKhCQCEISzT7Ssj7sbu//pFcXbI35ME+2Oh3zv1jZ+bO/X6/787Mzm9mLvAL/ArmbwK9gRDi94zRAiKkAABjKCJiiywWyxf+5tZTBJQAWq12kFrNFgDsEQCqDtkEsG2MsWyz2fytP/j1Bh2NuCwRHh7OJYnnqdWqEwCbrtFoVDMzZ6C09HOUln6OmZkzoNFoGEBpRM6jQuhe0+v1Bn/zDnhER0f345zPFYJXC8FJr5coI2Manfz2GLU0119wfXf6W8rKmkUGg56E4CQEr5MknqfT6SL8bUcgQsU5nyoEr2h3Jt0z+S76svQzN8d3vL7+6jBlZEyTRSAhhFmSdI8DCPK3UQEBznkK5/yI7MDx41No3949XTq+47Vv7x4aPz7FRQheJkm6NATYuOcz6HS6UULo9skOGzEiid4p3ErNTXWKDm6uryZzcSGZiwupub5auUxTHb1TuJVGjEhyFeJTIcQ4f9srw+9vg06nG6pSscUAJrX/xowZj2L6Xx9GSEiI+w1EqD/6OWy7tsFeZQYAqCO00N48CeHDbwJU7v8r7HY7Nm/eimXL8lFpMsnJ/3Y6ab7NZjvqJdO6Bb8JwDmPYYy9AFAGAHW/fv3w0IPTMGvWLISHhyne03jqKGzvb0XL92cU84OlgdAm343+141QzG9oaMC69Rvw6quvoqamFgAcAHudiJ6zWq3nPGJYD+FzASRJCnM6nTMZQw6AsODgYKSnp+Gpp56EXpIU72k+V46qXW+jsbysW8/QDEqANvVeaK5KVMy32apQUFCAVavXoqWlBQAaGcOK1lZHXnV1dXXvLOsdfClAiBAiA6AXAegZY7jjjjswf97TiIuLU7zBXm1FVfE7qDt8ECDq8QOvGPI78AnpCObKUwKj0YgVK1/Fm29uhtPpBAAbEZaEh4cvP336dFOPH9gL+EIAJkm6e4mQB7B4AEhKSsIzOfMxYoRyV+Gor0PNx//F+U+KQPbWS3u4Wo2wYWMQmXw3gsIGKJY5fPgIFi3Kxf6PD8hJZwG20GKxrAXguCQCXfHzZuWc8xTG8BKA4QCQmDgYTz05GxMnTlQs72xpRs2hYpzf9x84mz37AqpCQhExMhkDxt0JVahGscyHH+3Hiy8uxNdf/zguf80YvWA22972KBkXeEUAvV6fSuTYRAQ9AAwcOBCzsx7HlCnpUKvVbuXJ4UBd6X5UF/8L9rrz3qD0I9T9wxDxhz9iwKgUsKBgt3yn04mdO3di4aI8nDnTNtgzBhNj6gdMJtNuT/PxuAB6vX680+l4DwCLjByAzMxMPPLwg9BolN+6huOHYXtvM1qtlZ6mclEERXJok+9B2G9HAczdDS0tLdi6dRuWLHkZFqsVAEilUk8wmUy7PMnD4wIIwc8BiH5ydhaeeDILjCnH+5rKy2DbvQ3NxlOeptAjhMbGQ5d6LzRx1yjmEzmRv+wVLPvHKwBQYbFYYzz5fG8I4ATAjGfLoVK5dzctpnOo2vU2Gk4c8fSjLwn9En8D7fg0hOjd/et0OhB7ZRwAkMVi9WgE2RvhaAZA0fmBChdbPP7C+jxCGKKPgeGBrIDpgrwNb3RBBAAV55TDBR3ReOoobDvfQovJt5GAYB4Fbcpk9B+apDgId0R0zCAAgMVi9ajPfLYitmnTm3A43Oc0V8QPRXTm8xCTpnU6UfIk1P3DoB2fhpiZL7TFjDo43+l0YseOHV7nIcNnLSA6ZhAGD07A00890fVE7MMdcDY1epRXTydiSvwBz7cAnwogIykpCTkL5mHkyJGKdTga6lCz33ehiC+/PIzc3MWuoYg+LYAJaJshp6am4PnnnrloMK76wx2o/eLDngfjGEP/oUnQptxz0WDcy0uXYdu2QlBb/TYAuovxD3gBGFOFt4ejFwAI7144+jSqdm3tWTh6fBo0gwYr5ttsNhQUvOYajm5gDCtbWx15QUHqqovxD3gBZAO8syATDW3yXV0uyKxcuRK1tXWAwoJMd/l7Cn4TQIYvliRbW1uxZcvb3VqS/NkJIEOn041SqfASwMYCQFxcHLLnz8Htt98OpvA/neytqPm0GAAQcUOyYmSTiLBz507kLl6C8vJyOfkQwOZYLJZ9nuTfW1w2AsjgnKcAyGcM1wPA8OHDsCB7Pm68cVSPeBw8+AkW5S5GSUmpnEQA3g0JCf1LRUVFg7f49xSX3dZEq9VaZLVahwNsBoDvS0pK8ad7/4ypUzNQVna8y/vLyo5j6tQMFBS8hrVrVqG05DNkZEyFSqViAO5uaWn6n16vu83rhvgL8v6bjnt05PSe1HUpWxPPfHfygvxPDu6nsWPHuOwP0r0pSVKUN/l3B5ddF6SE8PBwrtGEPE2ExwFoNBoNTp08cUGZ+F8loqnpp2XMki8OISrqQv/a7XasXrMOy5blo6GhAQCqATbPYrGsBuD0Fv+L4bLrgpRQW1trNZut8xwO5xAAFzhahpzGGHYBwNx52fLenx8RFBSEx2ZMx94PipByazIARAJUwDn/SAgxxMtmKCIgBJBRVVXVZYiViM0CULV7dxHGjr0Z27YVupWJjY3Fxo2vY8OG9YiOjgZjGA3QQSGE8kYiLyKgBOgOLBbLCYCNIsIBk9mMvz2ehalTM2A0Gt3KpqbciuI9u+XWoGWMFvuab58TAGgTwWq13tT+T6q6aE8xbr4lBf8sWAW73X5B2YiIcCxZkgcAkI88+RJ9UoBAQp88tCCESCSi9QCNBoCUW5ORm7sQsbGxbmVramoxZ848APJhP9zjS659rgW0DaT0CWMYrZckrFj+CjZufF3R+buL9iD51lQU7SkGgCoiNt/XfPtcC2CMFhNBm5qagpUrliMiItytjNFoRHZ2jux4EOEAY+whi8VyQgjuU759TgB5IH0pL9fN+Z1NxKzWnyZivkafE6AzlJSUYs7ceTh69Fh7Cr3FmPoJs9n8gz95+XwMMBgM8d6snzEUAcDcufNRWVmJyspKZC/IwaS7Jrc7n06pVDTBYrHd39H53uamBJ+1AI1Gg6amJjgc9jIhdOtVqqBnTSaTx3fktg2kdMvuoj3aYcMvWBlrZQzLQkP7/d1oNF6w5UKONTkc9iyZq6/gsxbw8f59eOCB+6FWq4MBNt3pdJz0xkFqeSbMGAoB1AKoZQyFDodzuNlsne/qfDnaGhoacpIIc1UqVejEiROxb2+RJyn5FkLw80rhYPk6criE7ktPcz02WiGEeBTdbI0eChcHCSEedT0Ifl96Gh05XKLI+cx3J+X6vXt4wROQJL5dCE5TpqSTqbKiJwepj3HOJ3dV/6UKwDmfLAQ/1t2D4KbKCpoyJZ2E4CRJfLtnvORFCCESheA2ITglJMRTfv5Sqjlv7dTAoqL3acyY0T86UJL4AUnSjum8/t4JIEnaMZLED8jlRo4cQYXbt3R6ELyutorWrV1NQ4YMluu2+SNa2iu0i1AkG3v99b+mdWtXU2NDjaKxDfXnad3a1XTd0GtdW8S7Op1uqHvdPRNAp9MNFYK/K+cnJg6m/PylVFdbpcilqbGWCrdvoWHDhrlyKQoY57uCc54iBP9CNuTGG2+gwu1bOm0N1VVmys9fSvHxV8uGO4QQW6OiIq+W6+yuAJzzGCF0rwnBW4XgNGjQlfT8czlktVRetDWOG3eTq+O/av++RECDSZIuTQj+rWzYhAm30f6PPujUET98b6Tnn8uh6OiBsiMaJInnRUZGRnYlgCRJYe3ryLVCcBo4MIqysmaR0Xi60+cd+vQA3X3Xna6OPyOEmA6g75wySUhICBVCN1uSuEU2dMqUdDpe9lWnjjl18jhlZc0ivV6SxwdL1wK0lZEkQRkZ07qs/7HHppMkCZd7dbMTEhJC/e0vr8HlDa3p7hv62aGDHd/QTgWQW9jH+/depIWd7djC6uUW5m//+AxtfbRYIwS396aPVhJg9OhR3Rpj4uKukh1vF0Ks4Zx79ORjQKHjv5RrrxlCq1cVUH2d8jeAmhpradMbG9zSN72xgZoaaxXvqa+rptWrCujaa4Z0+S/rZ4uefrCpO1cgfLDpssMvnyy7PNCjWI3rdamxpl/gAoPB0F+SeI5oD/AZDHrKzJxBp8u/cXP86fJvKDNzhutnK89LEs8xGAz9/W1HwCMqKkrinC8XgjcLwSk2NoaefSabzp45RWfPnKJnn8mm2NgY2fHNnPPlUVFRyuedLjMEVH9oMBjiHY7WhQC7D+7cCaDNanVwTmVlpX+P3/cAASWAjPaPd2cTIRUAGMNuIpYbiB/v/gV+xv8Bhtk7GIFOT8EAAAAASUVORK5CYII="
11 },
12 "neutral": {
13 "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <title>Cache Tax</title>\n <g stroke=\"#faf9f5\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path fill=\"#92928b\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n <path d=\"m40 32 8 5-8 5\"/>\n </g>\n</svg>\n",
14 "png": "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"
15 }
16 },
17 "light": {
18 "warm": {
19 "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <title>Cache Tax</title>\n <g stroke=\"#141413\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path fill=\"#397527\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n <path d=\"m40 32 8 5-8 5\"/>\n </g>\n</svg>\n",
20 "png": "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"
21 },
22 "cold": {
23 "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <title>Cache Tax</title>\n <g stroke=\"#141413\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path fill=\"#c15f3c\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n <path d=\"m40 32 8 5-8 5\"/>\n </g>\n</svg>\n",
24 "png": "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"
25 },
26 "neutral": {
27 "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <title>Cache Tax</title>\n <g stroke=\"#141413\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path fill=\"#686861\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n <path d=\"m40 32 8 5-8 5\"/>\n </g>\n</svg>\n",
28 "png": "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"
29 }
30 }
31} as const
32