Claude's mood in the status line, from what the turn has read, written and broken

Claude Code mods (function-hook plugins) for working across many repos and many sessions at once — plus a pet.
| Mod | What it does |
|---|---|
| where-am-i | A band above the prompt: 📁 repo 🌿 branch ↑2 ± 3 changed 🎯 task. /task <text> names what this session is doing. |
| done-ping | A macOS notification (repo, duration, your prompt) with a sound per kind: 🔔 Glass when a turn over 30 s is done, Basso when it failed, Submarine when Claude is waiting on you. Change them in SOUNDS. |
| repo-guard | Blocks push --force, pushes to main/master, reset --hard, clean -f, checkout ., branch -D, stash clear, rm -rf ~. Asks before Claude first edits a repo other than the session's own. |
| claude-pet | A pet that earns xp from your work, grows through five stages and unlocks achievements. Pick a species (/pet species: chick, cat, dog, dragon, dino, ocean, bug, plant, robot, moon) or your own emojis (/pet emoji 🦊, /pet emoji 🥚 🐣 🦊 🐺 🐉). Lives in the status line (🦮 xixi (・_・)📖 Lv7 ▰▱▱▱▱▱) with 18 moods that follow Claude: 💭 thinking, 📖 reading, ✍️ writing, ⚡ running, 🧪 testing, 🌐 browsing, 📣 delegating, ✋ waiting for you, 🔥 on a 25-call streak, 🌙 past 1am, 🎉 tests pass, 📦 commit/push, (×_×) error, (╬ಠ益ಠ) three in a row, bored after 5 idle minutes, asleep after 15. /pet for stats, /pet name <name>. |
| claude-mood | Claude's mood in the status line: 🤔 reading, ✍️ writing, 🧪 testing, 😤 after a few errors, 😌 when done — with 📖 ✏️ ⚡ 💥 counters. |
| control-tower | A row under where-am-i's, shown only while another session waits for you (a permission or a question): ✋ backend-ng 在等你 2m [tower]. /tower (or the button) opens a pane listing every Claude session on this machine, named repo · branch for worktrees. |
| tldr | After every long answer, a dim 💡 TL;DR line (Haiku) in the transcript, so a session you switch back to reads at a glance. |
| sdk-sync | Release audit for the SpatialReal SDKs. Never acts while you work: when a new v* tag appears it reminds you once; /release-check <repo> [tag] gathers the facts (public API files changed, CHANGELOG entry, commits in each sibling SDK / docs / examples since the release, version pins) and asks Claude for a read-only audit table. /release-check status lists unchecked releases; --facts skips the audit. Graph: plugins/sdk-sync/hooks/sdk-map.ts. |
| fortune | Programmer jokes after the working spinner (Sauteing… 🎲 删库跑路前,记得先 git push。); the day's first prompt shows 今日运势 (/fortune). Fridays never deploy. |
At a Claude Code terminal prompt:
/plugin marketplace add YuehengHan/my-claude-mods
/plugin install where-am-i@my-claude-mods
/plugin install done-ping@my-claude-mods
/plugin install repo-guard@my-claude-mods
/plugin install claude-pet@my-claude-mods
/plugin install claude-mood@my-claude-mods
Pick the user scope so they load in every session. Install only the ones you want.
Add a local clone as the marketplace instead, and Claude Code reads the plugins straight from the folder:
claude plugin marketplace add ~/Desktop/my-claude-mods
claude plugin install where-am-i@my-claude-mods --scope user
Claude Code runs an installed copy, so after editing a mod bump its version in plugin.json, then:
claude plugin marketplace update my-claude-mods
claude plugin update <mod>@my-claude-mods
and run /reload-plugins (or restart) in each open session.
Check and test a mod:
claude plugin validate plugins/<mod>
claude plugin test plugins/<mod>hooks/register.ts 83 lines1import type { EngineInterface, Register } from 'claude-code'
2
3export type Tally = { reads: number; edits: number; runs: number; errors: number; agents: number }
4
5const READ_TOOLS: readonly string[] = ['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'LS']
6const EDIT_TOOLS: readonly string[] = ['Edit', 'Write', 'NotebookEdit', 'MultiEdit']
7
8const empty = (): Tally => ({ reads: 0, edits: 0, runs: 0, errors: 0, agents: 0 })
9
10let tally: Tally = empty()
11let current = ''
12
13const isTest = (c: string) => /\b(test|tests|pytest|jest|vitest|go\s+test|cargo\s+test)\b/.test(c)
14
15/** The mood while a turn runs: errors weigh most, then what the turn mostly does. */
16export function workingMood(t: Tally, now: string): string {
17 if (t.errors >= 5) return '😵💫 头晕了'
18 if (t.errors >= 3) return '😤 有点烦躁'
19 if (now === 'test') return '🧪 紧张地跑测试'
20 if (now === 'agent' || t.agents >= 2) return '🧑🤝🧑 在摇人帮忙'
21 if (t.edits >= 10 && t.errors === 0) return '🔥 手感火热'
22 if (t.reads >= 15 && t.edits === 0) return '🧐 深度调研中'
23 if (t.edits > 0 && t.edits >= t.reads) return '✍️ 奋笔疾书'
24 if (t.reads > 0) return '🤔 在读代码'
25 return '💭 思考中'
26}
27
28export function doneMood(t: Tally, reason: string, aborted: boolean): string {
29 if (aborted) return '🫢 被打断了'
30 if (reason !== 'answer') return '😭 挂了'
31 if (t.errors >= 3) return '😮💨 磕磕绊绊,总算搞定'
32 if (t.edits >= 10) return '😎 大干一场'
33 if (t.edits === 0 && t.reads === 0 && t.runs === 0) return '😊 随口一答'
34 return '😌 搞定'
35}
36
37export function counts(t: Tally): string {
38 const parts = [
39 t.reads ? `📖${t.reads}` : '',
40 t.edits ? `✏️${t.edits}` : '',
41 t.runs ? `⚡${t.runs}` : '',
42 t.errors ? `💥${t.errors}` : '',
43 ].filter(Boolean)
44 return parts.length ? ` ${parts.join(' ')}` : ''
45}
46
47function show($: EngineInterface, mood: string) {
48 $.ui.status(`${mood}${counts(tally)}`)
49}
50
51export const register: Register = on => {
52 on('prompt.submit', ($, e, next) => {
53 tally = empty()
54 current = ''
55 show($, workingMood(tally, current))
56 return next(e)
57 })
58
59 on('tool.call', async ($, e, next) => {
60 const tool = String(e.tool)
61 current = tool === 'Agent' || tool === 'Task' ? 'agent' : e.tool === 'Bash' && isTest(e.command) ? 'test' : ''
62 if (current === 'agent') tally.agents += 1
63 show($, workingMood(tally, current))
64
65 const ran = await next(e)
66 if (ran.deny === undefined) {
67 if (READ_TOOLS.includes(tool)) tally.reads += 1
68 else if (EDIT_TOOLS.includes(tool)) tally.edits += 1
69 else if (tool === 'Bash') tally.runs += 1
70 if (ran.isError) tally.errors += 1
71 }
72 current = ''
73 show($, workingMood(tally, current))
74 return ran
75 })
76
77 on('turn.complete', async ($, e, next) => {
78 const done = await next(e)
79 if (e.agentId === undefined) show($, doneMood(tally, e.reason, e.isAborted))
80 return done
81 })
82}
83