SLOPSHOPPER

where-am-i

A band above the prompt: repo, branch, uncommitted changes and this session's task

newbandguardcommandprocesstimer
v0.3.0no licenseupdated 2026-10-09YuehengHan/my-claude-mods/plugins/where-am-i
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · where-am-i
› 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 › /task ⎿ where-am-i: Task cleared. 📁 app 🎯 /task to name this task ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
📁 app 🎯 /task to name this task ⟨Claude Code's own drawing⟩
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 2 files
hooks/register.tsx 115 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3import type { RepoInfo } from '../types'
4
5const REPO = { plugin: 'where-am-i', key: 'repo' } as const
6const TASK = { plugin: 'where-am-i', key: 'task' } as const
7
8const basename = (path: string) => path.replace(/\/+$/, '').split('/').pop() || path
9
10export function parseStatus(out: string): Omit<RepoInfo, 'name'> {
11  let branch: string | null = null
12  let ahead = 0
13  let behind = 0
14  let dirty = 0
15  for (const line of out.split('\n')) {
16    if (line.startsWith('# branch.head ')) {
17      const head = line.slice('# branch.head '.length).trim()
18      branch = head === '(detached)' ? 'detached' : head
19    } else if (line.startsWith('# branch.ab ')) {
20      const m = /\+(\d+) -(\d+)/.exec(line)
21      if (m) {
22        ahead = Number(m[1])
23        behind = Number(m[2])
24      }
25    } else if (line.trim() !== '' && !line.startsWith('#')) {
26      dirty += 1
27    }
28  }
29  return { branch, dirty, ahead, behind }
30}
31
32async function refresh($: EngineInterface) {
33  const cwd = await $.session.cwd()
34  const top = await $.process.run(['git', 'rev-parse', '--show-toplevel'], { timeoutMs: 5000 })
35  if (top.exitCode !== 0) {
36    await $.state.set(REPO, { name: basename(cwd), branch: null, dirty: 0, ahead: 0, behind: 0 })
37    return
38  }
39  const status = await $.process.run(['git', 'status', '--porcelain=v2', '--branch'], { timeoutMs: 5000 })
40  const info: RepoInfo = { name: basename(top.stdout.trim()), ...parseStatus(status.stdout) }
41  await $.state.set(REPO, info)
42}
43
44function refreshQuietly($: EngineInterface) {
45  return refresh($).catch(() => undefined)
46}
47
48export const register: Register = on => {
49  on('session.start', async ($, e, next) => {
50    await $.command.register({
51      name: 'task',
52      description: "Name this session's task: shown above the prompt and in /tower, so you know what each session is for; empty clears it",
53      argumentHint: '[what this session is doing]',
54      immediate: true,
55    })
56    void refreshQuietly($)
57    // Catch branch switches and commits made outside Claude (your own terminal).
58    $.clock.every(20_000, () => void refreshQuietly($))
59    return next(e)
60  })
61
62  on('command.run', { command: 'task' }, async ($, e) => {
63    const text = e.args.trim()
64    await $.state.set(TASK, text)
65    return { text: text ? `🎯 Task: ${text}` : 'Task cleared.' }
66  })
67
68  on('turn.complete', async ($, e, next) => {
69    if (e.agentId === undefined) void refreshQuietly($)
70    return next(e)
71  })
72
73  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
74    const ran = await next(e)
75    if (/\bgit\b/.test(e.command)) void refreshQuietly($)
76    return ran
77  })
78
79  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
80    const { value: info = null } = await $.state.get(REPO)
81    if (e.props.hasSurvey || info === null) return next(e)
82    // Other mods' rows (control-tower) go beneath this one.
83    const rest = await next(e)
84    const { value: current = '' } = await $.state.get(TASK)
85    const { Box, Text } = $.ui.resolve(e)
86
87    const sync =
88      (info.ahead ? ` ↑${info.ahead}` : '') + (info.behind ? ` ↓${info.behind}` : '')
89
90    return (
91      <Box flexDirection="column">
92        <Box flexDirection="row" flexWrap="wrap">
93          <Text bold color="claude">📁 {info.name}</Text>
94          {info.branch !== null && (
95            <Text color={info.branch === 'main' || info.branch === 'master' ? 'warning' : 'suggestion'}>
96              {'  '}🌿 {info.branch}
97              {sync}
98            </Text>
99          )}
100          {info.branch !== null && (
101            <Text color={info.dirty ? 'warning' : 'success'}>
102              {'  '}
103              {info.dirty ? `± ${info.dirty} changed` : '✓ clean'}
104            </Text>
105          )}
106          <Text dimColor={!current}>
107            {'  '}🎯 {current || '/task to name this task'}
108          </Text>
109        </Box>
110        {rest}
111      </Box>
112    )
113  })
114}
115
types/index.d.ts 14 lines
1export type RepoInfo = {
2  name: string
3  branch: string | null
4  dirty: number
5  ahead: number
6  behind: number
7}
8
9declare module 'claude-code' {
10  interface PluginState {
11    'where-am-i': { repo: RepoInfo | null; task: string }
12  }
13}
14