SLOPSHOPPER

statusline

Status line for Claude Code: model, project, git, context, usage limits or API cost, duration. Works in the terminal and the desktop app.

newstatusprocess
v0.2.0no licenseupdated 2026-10-06dblanken-yale/statusline
A shopper browsing a rack in a slop shop
README

statusline

A Claude Code status line that works in the terminal and the desktop app.

[Opus 5.5]  πŸ“ my-project  |  🌿 main ●  |  βš™ .claude
β–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘ 25%  |  Sess 40% ↻6:23PM  Wk 10% ↻Fri 6PM  |  +12 -3  |  ⏱  1m 5s

What it shows

  • Line 1: model, project folder, git branch with status dots (green staged, red unstaged, blue untracked), and which Claude config folder the session uses. In a Lando project it adds a clickable link to the local site.
  • Line 2: context bar, then usage, lines changed, and active time (⏱).
  • On a Claude subscription, usage is your 5-hour session and weekly limits, colored green, yellow, or red, with reset times.
  • The ⏱ shows active time, not how long the window has been open. On desktop it is the sum of your turns. In the terminal it is the time Claude spent responding (model time), because that is what Claude Code gives a status line script.
  • With an API key or gateway, usage is dollar cost for the session, today, and this week, tracked locally in ~/.claude/cost-tracking.

The context bar turns yellow at 60% and red at 80%, with a hint to compact or hand off.

In the desktop app the mod shows this as a single plain line under the prompt, leaving out what the app already shows (branch, context, usage limits, lines changed) and the local site link.

Needs bash and jq.

Install

Run these in Claude Code:

/plugin marketplace add dblanken-yale/statusline
/plugin install statusline@statusline

Or from a terminal:

claude plugin marketplace add dblanken-yale/statusline
claude plugin install statusline@statusline

That covers the desktop app. For the terminal, also point statusLine in ~/.claude/settings.json at the script the marketplace keeps:

{
  "statusLine": {
    "type": "command",
    "command": "bash ~/.claude/plugins/marketplaces/statusline/statusline.sh"
  }
}

Start a new Claude Code session.

Customize

Create ~/.claude/statusline.conf (or point CLAUDE_STATUSLINE_CONFIG at another file). The script sources it after its defaults, so anything you set there wins and survives updates:

THRESHOLD_YELLOW=50
ICON_PROJECT="dir:"
detect_site_url() { :; }   # turn off the local site link

See the CONFIG block at the top of statusline.sh for everything you can set.

Update

Turn on auto-update for the statusline marketplace in /plugin (Marketplaces tab), and new versions install when Claude Code starts. To update by hand:

claude plugin marketplace update statusline

The terminal status line reads the marketplace copy, so it updates along with it.

Develop

git clone git@github.com:dblanken-yale/statusline.git ~/code/statusline
claude --plugin-dir ~/code/statusline

Disable the marketplace install while you do this, or the mod runs twice. To try script changes in the terminal, point statusLine at ~/code/statusline/statusline.sh.

Bump version in .claude-plugin/plugin.json with every release. Installed copies only update when the version changes.

To check it after editing: claude plugin validate ~/code/statusline and claude plugin test ~/code/statusline.

Source 2 files
hooks/statusline.mjs 80 lines
1import { atom, read, update } from 'claude-code'
2
3// Shows the bundled statusline.sh output through $.ui.status on the desktop app.
4const ANSI = /\x1b\[[0-9;]*m/g
5const OSC8 = /\x1b\]8;;[^\x07]*\x07/g
6// Segments the desktop app already shows (git branch, context bar, Sess/Wk usage), the
7// +N -N count, which we cannot supply, and the site link, which plain status text cannot
8// make clickable.
9const DROP = /^(🌿 |[β–ˆβ–‘]+ \d+%|(Sess|Wk) |\+\d+ -\d+$|πŸ”— )/
10const MODEL = /^\[[^\]]*\]\s*/
11
12const activeMs = atom({ plugin: 'statusline', key: 'activeMs' }, 0)
13
14async function refresh($, usage) {
15  try {
16    // The terminal already runs the script through settings.json statusLine.
17    if ((await $.session.surfaces()).includes('terminal')) return
18    const { context, rateLimits, cost } = usage
19    const percent = context?.percent
20    const input = {
21      workspace: { current_dir: await $.session.cwd() },
22      model: { display_name: await $.session.model() },
23      session_id: await $.session.id(),
24      cost: {
25        total_cost_usd: cost?.usd ?? 0,
26        active_duration_ms: await read($, activeMs),
27      },
28      context_window: {
29        ...(percent !== undefined && { used_percentage: percent, remaining_percentage: 100 - percent }),
30      },
31    }
32    const limits = {}
33    for (const r of rateLimits ?? []) {
34      if (r.kind !== 'five_hour' && r.kind !== 'seven_day') continue
35      limits[r.kind] = { used_percentage: r.percentUsed }
36      if (r.resetsAt) limits[r.kind].resets_at = Math.floor(Date.parse(r.resetsAt) / 1000)
37    }
38    if (Object.keys(limits).length) input.rate_limits = limits
39
40    const res = await $.process.run(['bash', `${$.plugin.root}/statusline.sh`], {
41      stdin: JSON.stringify(input),
42      timeoutMs: 5000,
43    })
44    if (res.exitCode !== 0) return
45    const text = res.stdout
46      .replace(OSC8, '')
47      .replace(ANSI, '')
48      .split(/\n|\s+\|\s+/)
49      .map(seg => seg.trim().replace(MODEL, ''))
50      .filter(seg => seg && !DROP.test(seg))
51      .join('  |  ')
52    await $.ui.status(text || undefined)
53  } catch {
54    // keep the previous status
55  }
56}
57
58export function register(on) {
59  on('session.start', async ($, e, next) => {
60    try {
61      await refresh($, await $.session.usage())
62    } catch {}
63    return next(e)
64  })
65  on('turn.complete', async ($, e, next) => {
66    const r = await next(e)
67    if (!e.agentId) {
68      await update($, activeMs, ms => ms + e.durationMs)
69      try {
70        await refresh($, await $.session.usage())
71      } catch {}
72    }
73    return r
74  })
75  on('session.measure', async ($, e, next) => {
76    await refresh($, e)
77    return next(e)
78  })
79}
80
types/index.d.ts 9 lines
1declare module 'claude-code' {
2  interface PluginState {
3    statusline: {
4      /** Sum of main-loop turn durations, in ms (desktop only). */
5      activeMs: number
6    }
7  }
8}
9