SLOPSHOPPER

context-zombie

Claude logo above the prompt that decays as context fills, and heals on compact or clear

newspinnercommandtimer
v0.1.0no licenseupdated 2026-10-15thepragmaticbear/context-zombie
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · context-zombie
› 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 › /zombie ⎿ context-zombie: Zombie override off; tracking real context. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ▐▛███▜▌ 49% ▝▜█████▛▘ ▘▘ ▝▝

Draws

Prompt hint
▐▛███▜▌ 49% ▝▜█████▛▘ ▘▘ ▝▝
README

context-zombie

A Claude Code mod: the Claude logo sits above the prompt (top right) and decays as context fills.

ContextLook
0-30%Normal orange
30-65%Green zombie, growing blood
65-90%Bone spreads from the top-left of the head
90%+Full skeleton

<img src="assets/0.svg" width="150" alt="0-30%"> <img src="assets/40.svg" width="150" alt="30-55%"> <img src="assets/60.svg" width="150" alt="55-65%"> <img src="assets/70.svg" width="150" alt="65-80%"> <img src="assets/80.svg" width="150" alt="80-90%"> <img src="assets/90.svg" width="150" alt="90%+">

It heals when you /compact or /clear. Test with /zombie 0-100, reset with /zombie off.

Install

/plugin install context-zombie --marketplace thepragmaticbear/context-zombie

Answer y to add the marketplace, then pick the user scope.

Source 2 files
hooks/decay.tsx 98 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4const forced = atom({ plugin: 'context-zombie', key: 'forced' } as const, null)
5const percent = atom({ plugin: 'context-zombie', key: 'percent' } as const, 0)
6
7// Claude Code mascot: the shape never changes, only the colour.
8const LOGO = [' ▐▛███▜▌ ', '▝▜█████▛▘', '  ▘▘ ▝▝  ']
9
10// Stable pseudo-random 0..1 per cell, so blood stays put between redraws.
11const rnd = (r: number, c: number) => {
12  const x = Math.sin((r + 1) * 12.9898 + (c + 1) * 78.233) * 43758.5453
13  return x - Math.floor(x)
14}
15
16const BONE = 'rgb(230,225,205)'
17
18// percent -> rows of [char, color]: orange -> green -> bloody dark green,
19// then from 65% bone spreads from the top-left of the head until all skeleton at 90%.
20const zombie = (p: number): [string, string][][] => {
21  const skin = p < 30 ? 'claude' : p < 55 ? 'rgb(120,190,90)' : 'rgb(70,130,60)'
22  const blood = p < 30 ? 0 : Math.min(0.6, (p - 30) / 60) // share of cells gone red
23  const reach = p < 65 ? -1 : (p - 65) / 25 + 0.1 // bone front, 0 = top-left, 1 = bottom-right
24  return LOGO.map((row, r) =>
25    [...row].map((ch, c): [string, string] => {
26      if (ch === ' ') return [ch, skin]
27      if ((c + r * 2) / 12 < reach) return [ch, BONE]
28      if (rnd(r, c) < blood) {
29        // scorched cell: solid dark red (what ▒ looked like), other blood cells bright red
30        return r < 2 && rnd(c, r) < 0.5 ? ['█', 'rgb(107,25,25)'] : [ch, 'rgb(170,20,20)']
31      }
32      return [ch, skin]
33    }),
34  )
35}
36
37async function refresh($: any) {
38  try {
39    if ((await read($, forced)) !== null) return // /zombie override active
40    const { context } = await $.session.usage()
41    await update($, percent, () => context.percent ?? 0)
42  } catch {
43    // session not bound yet
44  }
45}
46
47export const register: Register = on => {
48  on('session.start', async ($, e, next) => {
49    await $.command.register({
50      name: 'zombie',
51      description: 'Fake the context percent to test the logo: /zombie 0-100, or /zombie off',
52    })
53    await refresh($)
54    $.clock.every(3000, () => refresh($)) // catches /clear and auto-compact too
55    return next(e)
56  })
57  on('command.run', { command: 'zombie' }, async ($, e) => {
58    const arg = e.args.trim()
59    if (arg === 'off' || arg === '') {
60      await update($, forced, () => null)
61      await refresh($)
62      return { text: 'Zombie override off; tracking real context.' }
63    }
64    const n = Math.min(100, Math.max(0, Number(arg)))
65    if (Number.isNaN(n)) return { text: 'Usage: /zombie 0-100 | off' }
66    await update($, forced, () => n)
67    await update($, percent, () => n)
68    return { text: `Context faked at ${n}%.` }
69  })
70  on('turn.complete', async ($, e, next) => (await refresh($), next(e)))
71  on('session.compact', async ($, e, next) => {
72    const out = await next(e)
73    await refresh($)
74    return out
75  })
76
77  on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
78    const p = await read($, percent)
79    const { Box, Text } = $.ui.resolve(e)
80    const rows = zombie(p)
81
82    return (
83      <Box flexDirection="row" justifyContent="flex-end" width="100%">
84        <Box flexDirection="column">
85          {rows.map(row => (
86            <Box flexDirection="row">
87              {row.map(([ch, color]) => (
88                <Text color={color}>{ch}</Text>
89              ))}
90            </Box>
91          ))}
92        </Box>
93        <Text dimColor> {Math.round(p)}%</Text>
94      </Box>
95    )
96  })
97}
98
types/state.d.ts 8 lines
1export type Percent = number
2
3declare module 'claude-code' {
4  interface PluginState {
5    'context-zombie': { percent: number; forced: number | null }
6  }
7}
8