SLOPSHOPPER

claude-mood

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

newguardstatusprompt
v0.3.0no licenseupdated 2026-10-09YuehengHan/my-claude-mods/plugins/claude-mood
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · claude-mood
› 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 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ claude-mood: 😌 搞定 📖2 ✏️3 ⚡4 💥1
README

my-claude-mods

Claude Code mods (function-hook plugins) for working across many repos and many sessions at once — plus a pet.

ModWhat it does
where-am-iA band above the prompt: 📁 repo 🌿 branch ↑2 ± 3 changed 🎯 task. /task <text> names what this session is doing.
done-pingA 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-guardBlocks 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-petA 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-moodClaude's mood in the status line: 🤔 reading, ✍️ writing, 🧪 testing, 😤 after a few errors, 😌 when done — with 📖 ✏️ ⚡ 💥 counters.
control-towerA 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.
tldrAfter every long answer, a dim 💡 TL;DR line (Haiku) in the transcript, so a session you switch back to reads at a glance.
sdk-syncRelease 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.
fortuneProgrammer jokes after the working spinner (Sauteing… 🎲 删库跑路前,记得先 git push。); the day's first prompt shows 今日运势 (/fortune). Fridays never deploy.

Install

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.

Developing

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>
Source 1 files
hooks/register.ts 83 lines
1import 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