A plant in the band above the prompt that grows with every tool call and finished turn, wilts when a tool fails, and keeps growing across sessions.

A plant in the band above the prompt that grows as you work with Claude. Every successful tool call is a growth point, every finished turn is four, and a failed tool call takes two away and wilts the plant until the next turn finishes. It never dies, and it keeps growing across sessions.
🌿 seedling · 61 pts · 14 to bush · 12 turns Hide
Stages: 🫘 seed, 🌱 sprout, 🌿 seedling, 🪴 bush, 🌸 bloom, 🌳 tree. Reaching one shows a toast. Wilted, the plant is drawn 🥀 in yellow.
/plugin marketplace add Hula-Hoop-AI/supermods
/plugin install garden@supermods
The band is on from the first turn. /garden reports the plant's stage, points and turns, and hides the band; /garden again shows it. The band's Hide button (g while the band has focus) hides it too.
| Option | Default | Effect |
|---|---|---|
stage_points | 25 | Growth points needed to reach each next stage. |
Events: session.start (loads the saved garden, registers /garden), tool.call for every tool (observes whether the call failed; nothing is changed), turn.complete (main conversation turns that ended with an answer), command.run (/garden), ui.render (the band above the prompt).
Capabilities: $.store (the garden, one small record, written after each finished turn), $.ui.toast, $.state. No file, process, network or model access.
hooks/register.tsx 123 lines1import { atom, read, update } from 'claude-code';
2import type { EngineInterface, Register } from 'claude-code';
3
4import type { Garden } from '../types';
5
6const COMMAND = 'garden';
7const STORE_KEY = 'garden';
8const TOOL_POINTS = 1;
9const TURN_POINTS = 4;
10const FAILURE_POINTS = -2;
11const STAGES: [string, string][] = [
12 ['🫘', 'seed'],
13 ['🌱', 'sprout'],
14 ['🌿', 'seedling'],
15 ['🪴', 'bush'],
16 ['🌸', 'bloom'],
17 ['🌳', 'tree'],
18];
19const WILTED = '🥀';
20const EMPTY: Garden = { points: 0, isWilted: false, turns: 0 };
21
22const garden = atom({ plugin: 'garden', key: 'garden' } as const, EMPTY);
23const isHidden = atom({ plugin: 'garden', key: 'isHidden' } as const, false);
24
25function isGarden(value: unknown): value is Garden {
26 return typeof value === 'object' && value !== null && typeof (value as Garden).points === 'number';
27}
28
29function plural(count: number, noun: string): string {
30 return `${count} ${noun}${count === 1 ? '' : 's'}`;
31}
32
33export function stageOf(points: number, stagePoints: number): number {
34 return Math.min(STAGES.length - 1, Math.floor(points / stagePoints));
35}
36
37function stageAt(stage: number): [string, string] {
38 return STAGES[stage]!;
39}
40
41export function describe({ points, isWilted, turns }: Garden, stagePoints: number): { glyph: string; text: string } {
42 const stage = stageOf(points, stagePoints);
43 const [glyph, name] = stageAt(stage);
44 const next = STAGES[stage + 1];
45 const parts = [isWilted ? `${name}, wilted` : name, plural(points, 'pt')];
46 if (next !== undefined) parts.push(`${(stage + 1) * stagePoints - points} to ${next[1]}`);
47 parts.push(plural(turns, 'turn'));
48 return { glyph: isWilted ? WILTED : glyph, text: parts.join(' · ') };
49}
50
51async function grow($: EngineInterface, stagePoints: number, delta: number, isTurn = false): Promise<void> {
52 // Computed inside the updater, so parallel tool calls each land their points.
53 let before = EMPTY;
54 const after = await update($, garden, current => {
55 before = current;
56 return {
57 points: Math.max(0, current.points + delta),
58 isWilted: delta < 0 ? true : isTurn ? false : current.isWilted,
59 turns: current.turns + (isTurn ? 1 : 0),
60 };
61 });
62 if (isTurn) await $.store.set(STORE_KEY, after);
63 const stage = stageOf(after.points, stagePoints);
64 if (stage > stageOf(before.points, stagePoints)) {
65 const [glyph, name] = stageAt(stage);
66 $.ui.toast(`${glyph} Your garden grew into a ${name}.`);
67 }
68}
69
70export const register: Register = (on, options) => {
71 const stagePoints = Number(options['stage_points']);
72
73 on('session.start', async ($, e, next) => {
74 const saved = await $.store.get(STORE_KEY);
75 if (isGarden(saved)) await update($, garden, () => saved);
76 await $.command.register({ name: COMMAND, description: 'Show or hide the garden, and report how it grows' });
77
78 return next(e);
79 });
80
81 on('tool.call', async ($, e, next) => {
82 const ran = await next(e);
83 if (ran.deny === undefined) await grow($, stagePoints, ran.isError === true ? FAILURE_POINTS : TOOL_POINTS);
84
85 return ran;
86 });
87
88 on('turn.complete', async ($, e, next) => {
89 if (e.reason === 'answer' && e.agentId === undefined) await grow($, stagePoints, TURN_POINTS, true);
90
91 return next(e);
92 });
93
94 on('command.run', { command: COMMAND }, async $ => {
95 const hidden = !(await read($, isHidden));
96 await update($, isHidden, () => hidden);
97 const { glyph, text } = describe(await read($, garden), stagePoints);
98
99 return { text: `${glyph} ${text}. ${hidden ? 'Garden hidden; /garden shows it again.' : 'Garden shown.'}` };
100 });
101
102 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
103 const [hidden, state] = await Promise.all([read($, isHidden), read($, garden)]);
104 if (e.props.hasSurvey || hidden) return next(e);
105
106 const { Box, Button, Text } = $.ui.resolve(e);
107 const { glyph, text } = describe(state, stagePoints);
108
109 const below = await next(e);
110 const row = (
111 <Box>
112 <Text>{glyph} </Text>
113 <Text color={state.isWilted ? 'yellow' : 'green'} dimColor={state.isWilted} wrap="truncate-end">
114 {text}{' '}
115 </Text>
116 <Button key="garden-hide" label="Hide" hotkey="g" plain dimColor onPress={() => update($, isHidden, () => true)} />
117 </Box>
118 );
119
120 return below ? <Box flexDirection="column">{row}{below}</Box> : row;
121 });
122};
123types/index.d.ts 12 lines1export type Garden = {
2 points: number;
3 isWilted: boolean;
4 turns: number;
5};
6
7declare module 'claude-code' {
8 interface PluginState {
9 'garden': { garden: Garden; isHidden: boolean };
10 }
11}
12