SLOPSHOPPER

cache-tax

Warns before a prompt that would reload a cold prompt cache, with a token and cost estimate. /keepwarm pings the session before the cache goes cold.

newbandcommandtoastprompttimer
v0.1.0no licenseupdated 2026-10-11KianaVega/KianaVega/mods/cache-tax
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · cache-tax
› 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 › /keepwarm ⎿ cache-tax: Keep-warm is on. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

Claude Code mods

Nine mods for Claude Code (2.1.287 or newer), written from the walkthrough video. Each lives in mods/<name>.

Install one, or all, from a terminal session:

/plugin install <name> --marketplace KianaVega/KianaVega
ModWhat it doesExtra model usage
savvy-progressProgress bar above the prompt and a live list of subagents with model, tokens and rough costnone
tool-skinsIcons and colors for tool calls in seven themes, switch with /skin <name>none
you-should-knowA second, small model reads long answers and flags anything important you might missone small call per long answer
file-treePane showing files Claude reads and edits, green once committed (/file-tree)none
cache-taxHolds a prompt that would reload a cold cache and shows an estimate. /keepwarm pings before the cache expiresnone, except keep-warm pings
blast-radiusStops recursive deletes, git clean, hard resets and shows the affected files before you press Proceednone
reflectOffers to save corrections as a rule in CLAUDE.mdone small call when you save
terminal-browser/browse <url or file> opens a pane with a screenshot (kitty or Ghostty) and the page textnone
replay-theaterReplay button after a turn that steps through each edit as a diff (/replay)none

Notes

  • Settings such as trigger phrases, price tables, skins and which model the watcher uses are constants at the top of each hooks/register.tsx. Edit and reload.
  • Cost figures are estimates from a built-in price table.
  • terminal-browser is a viewer built from a headless screenshot and page text. It is not a live, clickable browser.
  • Mods run in the terminal and the desktop app's Code tab, not the VS Code extension.
Source 2 files
hooks/register.tsx 99 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4// The cache lives about an hour on a subscription plan (5 minutes on the plain API).
5const CACHE_TTL_MS = 60 * 60 * 1000
6const WARN_AFTER_MS = 55 * 60 * 1000 // ping this long after the last request
7const KEEP_WARM_BY_DEFAULT = false
8const MIN_TOKENS_TO_WARN = 20_000
9const INPUT_USD_PER_MTOK = 3 // rough price for reloading uncached context
10const CACHE_WRITE_MULTIPLIER = 1.25
11
12const warning = atom({ plugin: 'cache-tax', key: 'warning' } as const, null as string | null)
13
14let lastActivity: number | undefined
15let isKeepingWarm = KEEP_WARM_BY_DEFAULT
16let isHeld = false
17let timer: { cancel: () => void } | undefined
18
19function arm($: EngineInterface) {
20  timer?.cancel()
21  if (!isKeepingWarm) return
22  timer = $.clock.after(WARN_AFTER_MS, async () => {
23    // A tiny message so the cache entry is read, and renewed, before it expires.
24    await $.prompt.submit({ text: 'Reply with the single word: warm.' })
25  })
26}
27
28export const register: Register = on => {
29  lastActivity = undefined
30  isHeld = false
31
32  on('session.start', async ($, e, next) => {
33    lastActivity = await $.clock.now()
34    await $.command.register({ name: 'keepwarm', description: 'Toggle pinging the session to keep the prompt cache warm' })
35    arm($)
36
37    return next(e)
38  })
39
40  on('command.run', { command: 'keepwarm' }, async $ => {
41    isKeepingWarm = !isKeepingWarm
42    arm($)
43
44    return { text: `Keep-warm is ${isKeepingWarm ? 'on' : 'off'}.` }
45  })
46
47  // Change: hold a prompt that would reload a cold cache until the person sends it again.
48  on('prompt.submit', async ($, e, next) => {
49    const now = await $.clock.now()
50    const idle = lastActivity === undefined ? 0 : now - lastActivity
51    const isCold = lastActivity !== undefined && idle > CACHE_TTL_MS
52
53    if (isCold && !isHeld) {
54      const usage = await $.session.usage()
55      const tokens = usage.context.tokens ?? 0
56      if (tokens === 0 || tokens >= MIN_TOKENS_TO_WARN) {
57        const cost = (tokens / 1_000_000) * INPUT_USD_PER_MTOK * CACHE_WRITE_MULTIPLIER
58        const size = tokens === 0 ? 'the whole conversation' : `about ${tokens.toLocaleString()} tokens`
59        const text = `Cache is cold (${Math.round(idle / 60000)} min idle). Sending reloads ${size}${tokens === 0 ? '' : `, roughly $${cost.toFixed(2)}`}. Send again to continue.`
60        isHeld = true
61        await update($, warning, () => text)
62        $.ui.toast(text, { timeoutMs: 20000 })
63
64        await $.prompt.fill({ text: e.text, mode: 'replace' })
65
66        return { drop: text }
67      }
68    }
69
70    isHeld = false
71    lastActivity = now
72    await update($, warning, () => null)
73    arm($)
74
75    return next(e)
76  })
77
78  on('turn.complete', async ($, e, next) => {
79    lastActivity = await $.clock.now()
80    arm($)
81
82    return next(e)
83  })
84
85  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
86    const text = await read($, warning)
87    if (e.props.hasSurvey || text === null) {
88      return next(e)
89    }
90    const { Box, Text } = $.ui.resolve(e)
91
92    return (
93      <Box>
94        <Text color="warning">{text}</Text>
95      </Box>
96    )
97  })
98}
99
types/index.d.ts 8 lines
1export type Warning = string | null
2
3declare module 'claude-code' {
4  interface PluginState {
5    'cache-tax': { warning: Warning }
6  }
7}
8