SLOPSHOPPER

statusline-band

A status line above the prompt: model, dir@branch, diff counts, context window usage and the 5-hour limit

newbandprocess
v0.1.1MITupdated 2026-10-08waylongo/statusline-band
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · statusline-band
› 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 claude-opus-5-5 | app@feat/auth-refresh (+14 -3) | 97k/200k (49%) | 5h 31% ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
claude-opus-5-5 | app@feat/auth-refresh (+14 -3) | 97k/200k (49%) | 5h 31%
README

statusline-band

A Claude Code mod that draws a status line above the prompt, in the terminal and in the Desktop app's Code tab.

Opus 5.5  |  myproject@main (+13 -3)  |  84k/200k (42%)  |  5h 37%

中文说明

What it shows

| Segment | Meaning | | :- | :- | | Opus 5.5 | The session's model, as /model shows it | | myproject@main | The session directory's name and its git branch; the branch is left out outside a repository | | (+13 -3) | Lines added and deleted in unstaged changes; left out when the tree is clean | | 84k/200k (42%) | Context tokens used, the model's window, and the percentage | | 5h 37% | How much of the 5-hour limit is used; 5h -- until the session's first response |

The colors are Solarized's accents, which read on a light and on a dark background. Both percentages turn orange at 70% and red at 90%.

The line is drawn again after each model response and whenever Claude Code measures the session. Git is read in the background, one read at a time, so a slow repository never holds a turn or the session's start.

Requirements

  • Claude Code v2.1.287 or later in the terminal, or v2.1.286 or later in the Desktop app's Code tab
  • git on your PATH, for the branch and the line counts

The mod draws in the terminal and in the Desktop app. Its hooks run elsewhere (the VS Code extension's chat panel, claude -p), but nothing is drawn there.

Install

In a Claude Code session in your terminal:

/plugin install statusline-band --marketplace waylongo/statusline-band

Or from your shell:

claude plugin marketplace add waylongo/statusline-band
claude plugin install statusline-band@statusline-band

Installed at the user scope from a terminal, it loads in the Desktop app's local sessions too. Sessions that are already open pick it up after /reload-plugins or a restart.

What it does on your machine

A mod is code that runs with your permissions, so here is all this one does:

  • Runs git rev-parse --abbrev-ref HEAD and git diff --numstat in the session's directory. Both only read.
  • Reads the session's model, working directory and usage figures from Claude Code.
  • Makes no network requests and writes no files.

You can check that before installing. Clone the repository and run:

claude plugin validate ./statusline-band

The hooks: and calls: lines list every event the mod handles and everything it asks Claude Code to do.

Known limitations

  • Without a subscription (an API key, Bedrock, Vertex) there is no 5-hour reading, so the last segment stays 5h --.
  • On Windows the directory segment shows the whole path instead of the directory's name.
  • In a narrow window the line wraps onto a second row.
  • The line counts cover unstaged changes only; they drop to zero after git add.
  • On a detached HEAD the branch reads HEAD.

Checked by hand in the macOS Desktop app. The terminal is covered by the test suite only.

Customize

Everything is in hooks/register.tsx, about 150 lines. The colors are the constants at the top, and colorFor holds the 70% and 90% thresholds.

Develop

claude --plugin-dir .      # load from this folder; saving a file reloads the mod
claude plugin validate .   # what the engine would refuse, before a session loads it
claude plugin test .       # run tests/*.test.tsx against the engine

Claude Code writes its type declarations to .claude-plugin/types/ each time it loads the mod from this folder, which is what tsconfig.json extends.

License

MIT

中文说明

一个 Claude Code mod:在输入框上方画一条状态栏,终端和桌面端 Code 标签页都能显示。

从左到右依次是:模型名、目录名@git 分支、未暂存改动的增删行数、上下文已用/窗口大小(百分比)、5 小时限额已用百分比。新会话在第一次回复之前没有限额读数,显示 5h --。两个百分比到 70% 变橙色,到 90% 变红色。

安装(在终端的 Claude Code 会话里输入):

/plugin install statusline-band --marketplace waylongo/statusline-band

需要 Claude Code v2.1.287 以上(桌面端 v2.1.286 以上)。它只运行两条只读的 git 命令,不联网、不写文件。已知限制见上面的 Known limitations。

Source 1 files
hooks/register.tsx 150 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3// Solarized's accents, which read on a light and on a dark background
4const BLUE = '#268bd2'
5const CYAN = '#2aa198'
6const GREEN = '#859900'
7const YELLOW = '#b58900'
8const ORANGE = '#cb4b16'
9const RED = '#dc322f'
10
11type Git = { branch: string; adds: number; dels: number }
12
13// Read again at session.start, which a reload raises too
14let git: Git | null = null
15let isReadingGit = false
16let isGitStale = false
17
18const compact = (tokens: number) =>
19  tokens >= 1_000_000
20    ? `${+(tokens / 1_000_000).toFixed(1)}m`
21    : tokens >= 1_000
22      ? `${Math.round(tokens / 1_000)}k`
23      : String(tokens)
24
25const colorFor = (percent: number) =>
26  percent >= 90 ? RED : percent >= 70 ? ORANGE : GREEN
27
28const readGit = async ($: EngineInterface): Promise<Git | null> => {
29  const head = await $.process.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
30
31  if (head.exitCode !== 0) {
32    return null
33  }
34
35  const diff = await $.process.run(['git', 'diff', '--numstat'])
36  let adds = 0
37  let dels = 0
38
39  for (const line of diff.stdout.split('\n')) {
40    // A binary file's counts are "-"
41    const [added, deleted] = line.split('\t')
42    adds += Number(added) || 0
43    dels += Number(deleted) || 0
44  }
45
46  return { branch: head.stdout.trim(), adds, dels }
47}
48
49// One read at a time: a call made meanwhile asks for one more read after it
50const refreshGit = async ($: EngineInterface) => {
51  if (isReadingGit) {
52    isGitStale = true
53
54    return
55  }
56
57  isReadingGit = true
58
59  try {
60    do {
61      isGitStale = false
62      git = await readGit($).catch(() => null)
63      $.ui.invalidate('ui.render')
64    } while (isGitStale)
65  } finally {
66    isReadingGit = false
67  }
68}
69
70export const register: Register = on => {
71  on('session.start', ($, e, next) => {
72    // Not awaited: a slow git must not hold the session's start
73    void refreshGit($)
74
75    return next(e)
76  })
77
78  on('turn.step', async function* ($, e, next) {
79    const response = yield* next(e)
80    $.ui.invalidate('ui.render')
81    // Not awaited: a slow git must not hold the turn
82    void refreshGit($)
83
84    return response
85  })
86
87  on('session.measure', ($, e, next) => {
88    $.ui.invalidate('ui.render')
89
90    return next(e)
91  })
92
93  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
94    if (e.props.hasSurvey) {
95      return next(e)
96    }
97
98    const [model, cwd, { context, rateLimits }] = await Promise.all([
99      $.session.model(),
100      $.session.cwd(),
101      $.session.usage(),
102    ])
103    const { Box, Text } = $.ui.resolve(e)
104    const percent = context.percent ?? 0
105    const fiveHour = rateLimits.find(limit => limit.kind === 'five_hour')
106
107    const row = [
108      <Text color={BLUE}>{model}</Text>,
109      <Text dimColor>  |  </Text>,
110      <Text color={CYAN}>{cwd.slice(cwd.lastIndexOf('/') + 1)}</Text>,
111    ]
112
113    if (git !== null) {
114      row.push(<Text dimColor>@</Text>, <Text color={GREEN}>{git.branch}</Text>)
115
116      if (git.adds + git.dels > 0) {
117        row.push(<Text dimColor> (</Text>)
118        if (git.adds > 0) row.push(<Text color={GREEN}>+{git.adds}</Text>)
119        if (git.adds > 0 && git.dels > 0) row.push(<Text> </Text>)
120        if (git.dels > 0) row.push(<Text color={RED}>-{git.dels}</Text>)
121        row.push(<Text dimColor>)</Text>)
122      }
123    }
124
125    row.push(
126      <Text dimColor>  |  </Text>,
127      <Text color={YELLOW}>
128        {compact(context.tokens ?? 0)}/{compact(context.window)}
129      </Text>,
130      <Text dimColor> (</Text>,
131      <Text color={colorFor(percent)}>{percent}%</Text>,
132      <Text dimColor>)</Text>,
133    )
134
135    row.push(
136      <Text dimColor>  |  5h </Text>,
137      // No reading before the session's first response
138      fiveHour === undefined ? (
139        <Text dimColor>--</Text>
140      ) : (
141        <Text color={colorFor(fiveHour.percentUsed)}>
142          {Math.round(fiveHour.percentUsed)}%
143        </Text>
144      ),
145    )
146
147    return <Box>{...row}</Box>
148  })
149}
150