Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Customize your status line changedstatusline

Nearest release: v2.1.304, published an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 19 Oct 2026 18:23 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.

Upstream edited
Recorded here
Lines+794added
Lines−826removed
From line 15 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits33to this page, all time

#### Last miss cause

The whole hunk

from line 15, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 15
1515 
1616Here's an example of a [multi-line status line](#display-multiple-lines) that displays git info on the first line and a color-coded context bar on the second.
1717 
18<Frame>
19 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-multiline.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=a9d0a2fe8e446d80b1abc46da3f93270" alt="A multi-line status line showing model name, directory, git branch on the first line, and a context usage progress bar with cost and duration on the second line" width="1224" height="262" data-path="images/statusline-multiline.png" />
20</Frame>
18![A multi-line status line showing model name, directory, git branch on the first line, and a context usage progress bar with cost and duration on the second line](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-multiline.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=a9d0a2fe8e446d80b1abc46da3f93270)
2119 
2220This page walks through [setting up a basic status line](#set-up-a-status-line), explains [how the data flows](#how-status-lines-work) from Claude Code to your script, lists [all the fields you can display](#available-data), and provides [ready-to-use examples](#examples) for common patterns like git status, cost tracking, and progress bars.
2321 
from line 72
7472 
7573This walkthrough shows what `/statusline` sets up for you by manually creating a status line that displays the current model, working directory, and context window usage percentage.
7674 
77<Note>Running [`/statusline`](#use-the-%2Fstatusline-command) with a description of what you want configures all of this for you automatically.</Note>
75> [!NOTE]
76>
77> Running [`/statusline`](#use-the-%2Fstatusline-command) with a description of what you want configures all of this for you automatically.
7878 
7979These examples use Bash scripts, which work on macOS and Linux. On Windows, see [Windows configuration](#windows-configuration) for PowerShell and Git Bash examples.
8080 
81<Frame>
82 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-quickstart.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=88a7eab9c1038dd098ee8e284d96b7e6" alt="A status line showing model name, directory, and context percentage" width="1224" height="224" data-path="images/statusline-quickstart.png" />
83</Frame>
84 
85<Steps>
86 <Step title="Create a script that reads JSON and prints output">
87 Claude Code sends JSON data to your script via stdin. This script uses [`jq`](https://jqlang.org/), a command-line JSON parser you may need to install, to extract the model name, directory, and context percentage, then prints a formatted line.
88 
89 Save this to `~/.claude/statusline.sh` (where `~` is your home directory, such as `/Users/username` on macOS or `/home/username` on Linux):
90 
91 ```bash theme={null}
92 #!/bin/bash
93 # Read JSON data that Claude Code sends to stdin
94 input=$(cat)
95 
96 # Extract fields using jq
97 MODEL=$(echo "$input" | jq -r '.model.display_name')
98 DIR=$(echo "$input" | jq -r '.workspace.current_dir')
99 # The "// 0" provides a fallback if the field is null
100 PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
101 
102 # Output the status line - ${DIR##*/} extracts just the folder name
103 echo "[$MODEL] 📁 ${DIR##*/} | ${PCT}% context"
104 ```
105 </Step>
106 
107 <Step title="Make it executable">
108 Mark the script as executable so your shell can run it:
109 
110 ```bash theme={null}
111 chmod +x ~/.claude/statusline.sh
112 ```
113 </Step>
114 
115 <Step title="Add to settings">
116 Tell Claude Code to run your script as the status line. Add this configuration to `~/.claude/settings.json`, which sets `type` to `"command"` (meaning "run this shell command") and points `command` to your script:
117 
118 ```json theme={null}
119 {
120 "statusLine": {
121 "type": "command",
122 "command": "~/.claude/statusline.sh"
123 }
124 }
125 ```
126 
127 Your status line appears at the bottom of the interface. Claude Code reloads settings automatically and runs your script as soon as you save the file.
128 </Step>
129</Steps>
81![A status line showing model name, directory, and context percentage](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-quickstart.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=88a7eab9c1038dd098ee8e284d96b7e6)
82 
83**1. Create a script that reads JSON and prints output**
84 
85Claude Code sends JSON data to your script via stdin. This script uses [`jq`](https://jqlang.org/), a command-line JSON parser you may need to install, to extract the model name, directory, and context percentage, then prints a formatted line.
86 
87Save this to `~/.claude/statusline.sh` (where `~` is your home directory, such as `/Users/username` on macOS or `/home/username` on Linux):
88 
89```bash theme={null}
90#!/bin/bash
91# Read JSON data that Claude Code sends to stdin
92input=$(cat)
93 
94# Extract fields using jq
95MODEL=$(echo "$input" | jq -r '.model.display_name')
96DIR=$(echo "$input" | jq -r '.workspace.current_dir')
97# The "// 0" provides a fallback if the field is null
98PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
99 
100# Output the status line - ${DIR##*/} extracts just the folder name
101echo "[$MODEL] 📁 ${DIR##*/} | ${PCT}% context"
102```
103 
104**2. Make it executable**
105 
106Mark the script as executable so your shell can run it:
107 
108```bash theme={null}
109chmod +x ~/.claude/statusline.sh
110```
111 
112**3. Add to settings**
113 
114Tell Claude Code to run your script as the status line. Add this configuration to `~/.claude/settings.json`, which sets `type` to `"command"` (meaning "run this shell command") and points `command` to your script:
115 
116```json theme={null}
117{
118 "statusLine": {
119 "type": "command",
120 "command": "~/.claude/statusline.sh"
121 }
122}
123```
124 
125Your status line appears at the bottom of the interface. Claude Code reloads settings automatically and runs your script as soon as you save the file.
130126 
131127## How status lines work
132128 
133129Claude Code runs your script with [JSON session data](#available-data) on stdin and displays whatever the script prints to stdout.
134130 
135<Note>The status line runs locally and does not consume API tokens. It temporarily hides during certain UI interactions, including the help menu and permission prompts.</Note>
131> [!NOTE]
132>
133> The status line runs locally and does not consume API tokens. It temporarily hides during certain UI interactions, including the help menu and permission prompts.
136134 
137135### When the status line updates
138136 
from line 208
210208| `worktree.original_cwd` | The directory Claude was in before entering the worktree |
211209| `worktree.original_branch` | Git branch checked out before entering the worktree. Absent for hook-based worktrees |
212210 
213<Accordion title="Full JSON schema">
214 Your status line command receives this JSON structure via stdin:
215 
216 ```json theme={null}
217 {
218 "cwd": "/current/working/directory",
219 "session_id": "abc123...",
220 "session_name": "my-session",
221 "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
222 "transcript_path": "/path/to/transcript.jsonl",
223 "model": {
224 "id": "claude-opus-5-5",
225 "display_name": "Opus"
226 },
227 "workspace": {
228 "current_dir": "/current/working/directory",
229 "project_dir": "/original/project/directory",
230 "added_dirs": [],
231 "git_worktree": "feature-xyz",
232 "repo": {
233 "host": "github.com",
234 "owner": "anthropics",
235 "name": "claude-code"
236 }
237 },
238 "version": "2.1.90",
239 "output_style": {
240 "name": "default"
241 },
242 "cost": {
243 "total_cost_usd": 0.01234,
244 "total_duration_ms": 45000,
245 "total_api_duration_ms": 2300,
246 "total_lines_added": 156,
247 "total_lines_removed": 23
248 },
249 "context_window": {
250 "total_input_tokens": 15500,
251 "total_output_tokens": 1200,
252 "context_window_size": 200000,
253 "used_percentage": 8,
254 "remaining_percentage": 92,
255 "current_usage": {
256 "input_tokens": 8500,
257 "output_tokens": 1200,
258 "cache_creation_input_tokens": 5000,
259 "cache_read_input_tokens": 2000
260 }
261 },
262 "exceeds_200k_tokens": false,
263 "prompt_cache": {
264 "warm": true,
265 "caching_observed": true,
266 "ttl": "1h",
267 "expires_at": 1738429200,
268 "requests": 14,
269 "misses": 2,
270 "expected_rebuilds": 1,
271 "hit_ratio": 0.91,
272 "cache_write_tokens": 352000,
273 "miss_recache_tokens": 310200,
274 "last_miss_at": 1738425230,
275 "last_miss_cause": {
276 "causes": ["tools_changed"],
277 "tools_added": 2,
278 "tools_removed": 0
279 },
280 "miss_causes": {
281 "tools_changed": 2
282 },
283 "recache_tokens_if_cold": 45000
284 },
285 "fast_mode": false,
286 "effort": {
287 "level": "high"
288 },
289 "thinking": {
290 "enabled": true
291 },
292 "rate_limits": {
293 "five_hour": {
294 "used_percentage": 23.5,
295 "resets_at": 1738425600
296 },
297 "seven_day": {
298 "used_percentage": 41.2,
299 "resets_at": 1738857600
300 },
301 "spend_limit": {
302 "used_percentage": 62.8,
303 "resets_at": 1740787200,
304 "used_usd": 314.12,
305 "limit_usd": 500,
306 "period": "monthly"
307 }
308 },
309 "vim": {
310 "mode": "NORMAL"
311 },
312 "agent": {
313 "name": "security-reviewer"
314 },
315 "pr": {
316 "number": 1234,
317 "url": "https://github.com/anthropics/claude-code/pull/1234",
318 "review_state": "pending"
319 },
320 "worktree": {
321 "name": "my-feature",
322 "path": "/path/to/.claude/worktrees/my-feature",
323 "branch": "worktree-my-feature",
324 "original_cwd": "/path/to/project",
325 "original_branch": "main"
326 }
327 }
328 ```
329 
330 **Fields that may be absent** (not present in JSON):
331 
332 * `session_name`: appears when a custom name has been set with `--name` or `/rename`, or once an AI-generated session title exists. The default display name, such as `my-app-3f`, doesn't populate it
333 * `prompt_id`: appears only after the first user input
334 * `workspace.git_worktree`: appears only when the current directory is inside a linked git worktree
335 * `workspace.repo`: appears only inside a git repository with an `origin` remote configured
336 * `effort`: appears only when the current model supports the reasoning effort parameter
337 * `vim`: appears only when vim mode is enabled
338 * `agent`: appears only when running with the `--agent` flag or agent settings configured
339 * `pr`: appears only while an open PR or GitLab merge request is found for the current branch, and is removed once it merges or closes. `pr.review_state` and `pr.kind` may be independently absent
340 * `worktree`: appears only while the session is in a [worktree session](/docs/en/worktrees). When present, `branch` and `original_branch` may also be absent for hook-based worktrees
341 * `rate_limits`: appears only for claude.ai Pro and Max subscribers, or behind a Claude apps gateway that sets a spend limit for you, and only after the first API response in the session. Each window (`five_hour`, `seven_day`, `spend_limit`) may be independently absent, and Claude Code drops a window once its `resets_at` time passes. Use `jq -r '.rate_limits.five_hour.used_percentage // empty'` to handle absence gracefully.
342 * `prompt_cache`: appears after the main conversation's first API response. See [prompt cache fields](#prompt-cache-fields)
343 
344 **Fields that may be `null`**:
345 
346 * `context_window.current_usage`: `null` before the first API call in a session, and again after `/compact` until the next API call repopulates it
347 * `context_window.used_percentage`, `context_window.remaining_percentage`: may be `null` early in the session
348 
349 Handle missing fields with conditional access and null values with fallback defaults in your scripts.
350</Accordion>
211> **Full JSON schema**
212>
213> Your status line command receives this JSON structure via stdin:
214>
215> ```json theme={null}
216> {
217> "cwd": "/current/working/directory",
218> "session_id": "abc123...",
219> "session_name": "my-session",
220> "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
221> "transcript_path": "/path/to/transcript.jsonl",
222> "model": {
223> "id": "claude-opus-5-5",
224> "display_name": "Opus"
225> },
226> "workspace": {
227> "current_dir": "/current/working/directory",
228> "project_dir": "/original/project/directory",
229> "added_dirs": [],
230> "git_worktree": "feature-xyz",
231> "repo": {
232> "host": "github.com",
233> "owner": "anthropics",
234> "name": "claude-code"
235> }
236> },
237> "version": "2.1.90",
238> "output_style": {
239> "name": "default"
240> },
241> "cost": {
242> "total_cost_usd": 0.01234,
243> "total_duration_ms": 45000,
244> "total_api_duration_ms": 2300,
245> "total_lines_added": 156,
246> "total_lines_removed": 23
247> },
248> "context_window": {
249> "total_input_tokens": 15500,
250> "total_output_tokens": 1200,
251> "context_window_size": 200000,
252> "used_percentage": 8,
253> "remaining_percentage": 92,
254> "current_usage": {
255> "input_tokens": 8500,
256> "output_tokens": 1200,
257> "cache_creation_input_tokens": 5000,
258> "cache_read_input_tokens": 2000
259> }
260> },
261> "exceeds_200k_tokens": false,
262> "prompt_cache": {
263> "warm": true,
264> "caching_observed": true,
265> "ttl": "1h",
266> "expires_at": 1738429200,
267> "requests": 14,
268> "misses": 2,
269> "expected_rebuilds": 1,
270> "hit_ratio": 0.91,
271> "cache_write_tokens": 352000,
272> "miss_recache_tokens": 310200,
273> "last_miss_at": 1738425230,
274> "last_miss_cause": {
275> "causes": ["tools_changed"],
276> "tools_added": 2,
277> "tools_removed": 0
278> },
279> "miss_causes": {
280> "tools_changed": 2
281> },
282> "recache_tokens_if_cold": 45000
283> },
284> "fast_mode": false,
285> "effort": {
286> "level": "high"
287> },
288> "thinking": {
289> "enabled": true
290> },
291> "rate_limits": {
292> "five_hour": {
293> "used_percentage": 23.5,
294> "resets_at": 1738425600
295> },
296> "seven_day": {
297> "used_percentage": 41.2,
298> "resets_at": 1738857600
299> },
300> "spend_limit": {
301> "used_percentage": 62.8,
302> "resets_at": 1740787200,
303> "used_usd": 314.12,
304> "limit_usd": 500,
305> "period": "monthly"
306> }
307> },
308> "vim": {
309> "mode": "NORMAL"
310> },
311> "agent": {
312> "name": "security-reviewer"
313> },
314> "pr": {
315> "number": 1234,
316> "url": "https://github.com/anthropics/claude-code/pull/1234",
317> "review_state": "pending"
318> },
319> "worktree": {
320> "name": "my-feature",
321> "path": "/path/to/.claude/worktrees/my-feature",
322> "branch": "worktree-my-feature",
323> "original_cwd": "/path/to/project",
324> "original_branch": "main"
325> }
326> }
327> ```
328>
329> **Fields that may be absent** (not present in JSON):
330>
331> * `session_name`: appears when a custom name has been set with `--name` or `/rename`, or once an AI-generated session title exists. The default display name, such as `my-app-3f`, doesn't populate it
332> * `prompt_id`: appears only after the first user input
333> * `workspace.git_worktree`: appears only when the current directory is inside a linked git worktree
334> * `workspace.repo`: appears only inside a git repository with an `origin` remote configured
335> * `effort`: appears only when the current model supports the reasoning effort parameter
336> * `vim`: appears only when vim mode is enabled
337> * `agent`: appears only when running with the `--agent` flag or agent settings configured
338> * `pr`: appears only while an open PR or GitLab merge request is found for the current branch, and is removed once it merges or closes. `pr.review_state` and `pr.kind` may be independently absent
339> * `worktree`: appears only while the session is in a [worktree session](/docs/en/worktrees). When present, `branch` and `original_branch` may also be absent for hook-based worktrees
340> * `rate_limits`: appears only for claude.ai Pro and Max subscribers, or behind a Claude apps gateway that sets a spend limit for you, and only after the first API response in the session. Each window (`five_hour`, `seven_day`, `spend_limit`) may be independently absent, and Claude Code drops a window once its `resets_at` time passes. Use `jq -r '.rate_limits.five_hour.used_percentage // empty'` to handle absence gracefully.
341> * `prompt_cache`: appears after the main conversation's first API response. See [prompt cache fields](#prompt-cache-fields)
342>
343> **Fields that may be `null`**:
344>
345> * `context_window.current_usage`: `null` before the first API call in a session, and again after `/compact` until the next API call repopulates it
346> * `context_window.used_percentage`, `context_window.remaining_percentage`: may be `null` early in the session
347>
348> Handle missing fields with conditional access and null values with fallback defaults in your scripts.
351349 
352350### Context window fields
353351 
from line 405
407405 
408406Claude Code shows the same statistics in the terminal, on the [`/usage` command's `Prompt cache (main)` line](/docs/en/costs#prompt-cache-statistics).
409407 
410<h4 id="last-miss-cause">
411 Last miss cause
412</h4>
408#### Last miss cause
413409 
414410The `last_miss_cause` object reports what Claude Code identified as the likely cause of the most recent miss. Its `causes` array holds one or more cause names, such as `tools_changed`, `system_prompt_changed`, `ttl_expired_5m`, or `likely_server_side`. The object is `null` until the session's first miss, and again whenever Claude Code couldn't identify a cause for the most recent miss. Requires Claude Code v2.1.260 or later.
415411 
from line 428
432428 
433429Display the current model and context window usage with a visual progress bar. Each script reads JSON from stdin, extracts the `used_percentage` field, and builds a 10-character bar where filled blocks (▓) represent usage:
434430 
435<Frame>
436 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-context-window-usage.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=f3918a549912dc47e90f2b69e68bc847" alt="A status line showing model name and a progress bar with percentage" width="1224" height="224" data-path="images/statusline-context-window-usage.png" />
437</Frame>
438 
439<CodeGroup>
440 ```bash Bash theme={null}
441 #!/bin/bash
442 # Read all of stdin into a variable
443 input=$(cat)
444 
445 # Extract fields with jq, "// 0" provides fallback for null
446 MODEL=$(echo "$input" | jq -r '.model.display_name')
447 PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
448 
449 # Build progress bar: printf -v creates a run of spaces, then
450 # ${var// /▓} replaces each space with a block character
451 BAR_WIDTH=10
452 FILLED=$((PCT * BAR_WIDTH / 100))
453 EMPTY=$((BAR_WIDTH - FILLED))
454 BAR=""
455 [ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" && BAR="${FILL// /▓}"
456 [ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" && BAR="${BAR}${PAD// /░}"
457 
458 echo "[$MODEL] $BAR $PCT%"
459 ```
460 
461 ```python Python theme={null}
462 #!/usr/bin/env python3
463 import json, sys
464 
465 # json.load reads and parses stdin in one step
466 data = json.load(sys.stdin)
467 model = data['model']['display_name']
468 # "or 0" handles null values
469 pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
470 
471 # String multiplication builds the bar
472 filled = pct * 10 // 100
473 bar = '▓' * filled + '░' * (10 - filled)
474 
475 print(f"[{model}] {bar} {pct}%")
476 ```
477 
478 ```javascript Node.js theme={null}
479 #!/usr/bin/env node
480 // Node.js reads stdin asynchronously with events
481 let input = '';
482 process.stdin.on('data', chunk => input += chunk);
483 process.stdin.on('end', () => {
484 const data = JSON.parse(input);
485 const model = data.model.display_name;
486 // Optional chaining (?.) safely handles null fields
487 const pct = Math.floor(data.context_window?.used_percentage || 0);
488 
489 // String.repeat() builds the bar
490 const filled = Math.floor(pct * 10 / 100);
491 const bar = '▓'.repeat(filled) + '░'.repeat(10 - filled);
492 
493 console.log(`[${model}] ${bar} ${pct}%`);
494 });
495 ```
496</CodeGroup>
431![A status line showing model name and a progress bar with percentage](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-context-window-usage.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=f3918a549912dc47e90f2b69e68bc847)
432 
433```bash Bash theme={null}
434#!/bin/bash
435# Read all of stdin into a variable
436input=$(cat)
437 
438# Extract fields with jq, "// 0" provides fallback for null
439MODEL=$(echo "$input" | jq -r '.model.display_name')
440PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
441 
442# Build progress bar: printf -v creates a run of spaces, then
443# ${var// /▓} replaces each space with a block character
444BAR_WIDTH=10
445FILLED=$((PCT * BAR_WIDTH / 100))
446EMPTY=$((BAR_WIDTH - FILLED))
447BAR=""
448[ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" && BAR="${FILL// /▓}"
449[ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" && BAR="${BAR}${PAD// /░}"
450 
451echo "[$MODEL] $BAR $PCT%"
452```
453 
454```python Python theme={null}
455#!/usr/bin/env python3
456import json, sys
457 
458# json.load reads and parses stdin in one step
459data = json.load(sys.stdin)
460model = data['model']['display_name']
461# "or 0" handles null values
462pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
463 
464# String multiplication builds the bar
465filled = pct * 10 // 100
466bar = '▓' * filled + '░' * (10 - filled)
467 
468print(f"[{model}] {bar} {pct}%")
469```
470 
471```javascript Node.js theme={null}
472#!/usr/bin/env node
473// Node.js reads stdin asynchronously with events
474let input = '';
475process.stdin.on('data', chunk => input += chunk);
476process.stdin.on('end', () => {
477 const data = JSON.parse(input);
478 const model = data.model.display_name;
479 // Optional chaining (?.) safely handles null fields
480 const pct = Math.floor(data.context_window?.used_percentage || 0);
481 
482 // String.repeat() builds the bar
483 const filled = Math.floor(pct * 10 / 100);
484 const bar = '▓'.repeat(filled) + '░'.repeat(10 - filled);
485 
486 console.log(`[${model}] ${bar} ${pct}%`);
487});
488```
497489 
498490### Git status with colors
499491 
500492Show git branch with color-coded indicators for staged and modified files. This script uses [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) for terminal colors: `\033[32m` is green, `\033[33m` is yellow, and `\033[0m` resets to default.
501493 
502<Frame>
503 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-git-context.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=f13c190724d9ec7188c17cd2f98b7bf4" alt="A status line showing model, directory, git branch, and colored indicators for staged and modified files" width="1224" height="224" data-path="images/statusline-git-context.png" />
504</Frame>
494![A status line showing model, directory, git branch, and colored indicators for staged and modified files](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-git-context.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=f13c190724d9ec7188c17cd2f98b7bf4)
505495 
506496Each script checks if the current directory is a git repository, counts staged and modified files, and displays color-coded indicators:
507497 
508<CodeGroup>
509 ```bash Bash theme={null}
510 #!/bin/bash
511 input=$(cat)
512 
513 MODEL=$(echo "$input" | jq -r '.model.display_name')
514 DIR=$(echo "$input" | jq -r '.workspace.current_dir')
515 
516 GREEN='\033[32m'
517 YELLOW='\033[33m'
518 RESET='\033[0m'
519 
520 if git rev-parse --git-dir > /dev/null 2>&1; then
521 BRANCH=$(git branch --show-current 2>/dev/null)
522 STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
523 MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
524 
525 GIT_STATUS=""
526 [ "$STAGED" -gt 0 ] && GIT_STATUS="${GREEN}+${STAGED}${RESET}"
527 [ "$MODIFIED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}${YELLOW}~${MODIFIED}${RESET}"
528 
529 echo -e "[$MODEL] 📁 ${DIR##*/} | 🌿 $BRANCH $GIT_STATUS"
530 else
531 echo "[$MODEL] 📁 ${DIR##*/}"
532 fi
533 ```
534 
535 ```python Python theme={null}
536 #!/usr/bin/env python3
537 import json, sys, subprocess, os
538 
539 data = json.load(sys.stdin)
540 model = data['model']['display_name']
541 directory = os.path.basename(data['workspace']['current_dir'])
542 
543 GREEN, YELLOW, RESET = '\033[32m', '\033[33m', '\033[0m'
544 
545 try:
546 subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL)
547 branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True).strip()
548 staged_output = subprocess.check_output(['git', 'diff', '--cached', '--numstat'], text=True).strip()
549 modified_output = subprocess.check_output(['git', 'diff', '--numstat'], text=True).strip()
550 staged = len(staged_output.split('\n')) if staged_output else 0
551 modified = len(modified_output.split('\n')) if modified_output else 0
552 
553 git_status = f"{GREEN}+{staged}{RESET}" if staged else ""
554 git_status += f"{YELLOW}~{modified}{RESET}" if modified else ""
555 
556 print(f"[{model}] 📁 {directory} | 🌿 {branch} {git_status}")
557 except:
558 print(f"[{model}] 📁 {directory}")
559 ```
560 
561 ```javascript Node.js theme={null}
562 #!/usr/bin/env node
563 const { execSync } = require('child_process');
564 const path = require('path');
565 
566 let input = '';
567 process.stdin.on('data', chunk => input += chunk);
568 process.stdin.on('end', () => {
569 const data = JSON.parse(input);
570 const model = data.model.display_name;
571 const dir = path.basename(data.workspace.current_dir);
572 
573 const GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RESET = '\x1b[0m';
574 
575 try {
576 execSync('git rev-parse --git-dir', { stdio: 'ignore' });
577 const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
578 const staged = execSync('git diff --cached --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
579 const modified = execSync('git diff --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
580 
581 let gitStatus = staged ? `${GREEN}+${staged}${RESET}` : '';
582 gitStatus += modified ? `${YELLOW}~${modified}${RESET}` : '';
583 
584 console.log(`[${model}] 📁 ${dir} | 🌿 ${branch} ${gitStatus}`);
585 } catch {
586 console.log(`[${model}] 📁 ${dir}`);
587 }
588 });
589 ```
590</CodeGroup>
498```bash Bash theme={null}
499#!/bin/bash
500input=$(cat)
501 
502MODEL=$(echo "$input" | jq -r '.model.display_name')
503DIR=$(echo "$input" | jq -r '.workspace.current_dir')
504 
505GREEN='\033[32m'
506YELLOW='\033[33m'
507RESET='\033[0m'
508 
509if git rev-parse --git-dir > /dev/null 2>&1; then
510 BRANCH=$(git branch --show-current 2>/dev/null)
511 STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
512 MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
513 
514 GIT_STATUS=""
515 [ "$STAGED" -gt 0 ] && GIT_STATUS="${GREEN}+${STAGED}${RESET}"
516 [ "$MODIFIED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}${YELLOW}~${MODIFIED}${RESET}"
517 
518 echo -e "[$MODEL] 📁 ${DIR##*/} | 🌿 $BRANCH $GIT_STATUS"
519else
520 echo "[$MODEL] 📁 ${DIR##*/}"
521fi
522```
523 
524```python Python theme={null}
525#!/usr/bin/env python3
526import json, sys, subprocess, os
527 
528data = json.load(sys.stdin)
529model = data['model']['display_name']
530directory = os.path.basename(data['workspace']['current_dir'])
531 
532GREEN, YELLOW, RESET = '\033[32m', '\033[33m', '\033[0m'
533 
534try:
535 subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL)
536 branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True).strip()
537 staged_output = subprocess.check_output(['git', 'diff', '--cached', '--numstat'], text=True).strip()
538 modified_output = subprocess.check_output(['git', 'diff', '--numstat'], text=True).strip()
539 staged = len(staged_output.split('\n')) if staged_output else 0
540 modified = len(modified_output.split('\n')) if modified_output else 0
541 
542 git_status = f"{GREEN}+{staged}{RESET}" if staged else ""
543 git_status += f"{YELLOW}~{modified}{RESET}" if modified else ""
544 
545 print(f"[{model}] 📁 {directory} | 🌿 {branch} {git_status}")
546except:
547 print(f"[{model}] 📁 {directory}")
548```
549 
550```javascript Node.js theme={null}
551#!/usr/bin/env node
552const { execSync } = require('child_process');
553const path = require('path');
554 
555let input = '';
556process.stdin.on('data', chunk => input += chunk);
557process.stdin.on('end', () => {
558 const data = JSON.parse(input);
559 const model = data.model.display_name;
560 const dir = path.basename(data.workspace.current_dir);
561 
562 const GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RESET = '\x1b[0m';
563 
564 try {
565 execSync('git rev-parse --git-dir', { stdio: 'ignore' });
566 const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
567 const staged = execSync('git diff --cached --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
568 const modified = execSync('git diff --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
569 
570 let gitStatus = staged ? `${GREEN}+${staged}${RESET}` : '';
571 gitStatus += modified ? `${YELLOW}~${modified}${RESET}` : '';
572 
573 console.log(`[${model}] 📁 ${dir} | 🌿 ${branch} ${gitStatus}`);
574 } catch {
575 console.log(`[${model}] 📁 ${dir}`);
576 }
577});
578```
591579 
592580### Cost and duration tracking
593581 
from line 583
595583 
596584Each script formats cost as currency and converts milliseconds to minutes and seconds:
597585 
598<Frame>
599 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-cost-tracking.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=925f7024c3b38be0f0eca63564bfb52f" alt="A status line showing model name, session cost, and duration" width="1224" height="224" data-path="images/statusline-cost-tracking.png" />
600</Frame>
601 
602<CodeGroup>
603 ```bash Bash theme={null}
604 #!/bin/bash
605 input=$(cat)
606 
607 MODEL=$(echo "$input" | jq -r '.model.display_name')
608 COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
609 DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
610 
611 COST_FMT=$(printf '$%.2f' "$COST")
612 DURATION_SEC=$((DURATION_MS / 1000))
613 MINS=$((DURATION_SEC / 60))
614 SECS=$((DURATION_SEC % 60))
615 
616 echo "[$MODEL] 💰 $COST_FMT | ⏱️ ${MINS}m ${SECS}s"
617 ```
618 
619 ```python Python theme={null}
620 #!/usr/bin/env python3
621 import json, sys
622 
623 data = json.load(sys.stdin)
624 model = data['model']['display_name']
625 cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
626 duration_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
627 
628 duration_sec = duration_ms // 1000
629 mins, secs = duration_sec // 60, duration_sec % 60
630 
631 print(f"[{model}] 💰 ${cost:.2f} | ⏱️ {mins}m {secs}s")
632 ```
633 
634 ```javascript Node.js theme={null}
635 #!/usr/bin/env node
636 let input = '';
637 process.stdin.on('data', chunk => input += chunk);
638 process.stdin.on('end', () => {
639 const data = JSON.parse(input);
640 const model = data.model.display_name;
641 const cost = data.cost?.total_cost_usd || 0;
642 const durationMs = data.cost?.total_duration_ms || 0;
643 
644 const durationSec = Math.floor(durationMs / 1000);
645 const mins = Math.floor(durationSec / 60);
646 const secs = durationSec % 60;
647 
648 console.log(`[${model}] 💰 $${cost.toFixed(2)} | ⏱️ ${mins}m ${secs}s`);
649 });
650 ```
651</CodeGroup>
586![A status line showing model name, session cost, and duration](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-cost-tracking.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=925f7024c3b38be0f0eca63564bfb52f)
587 
588```bash Bash theme={null}
589#!/bin/bash
590input=$(cat)
591 
592MODEL=$(echo "$input" | jq -r '.model.display_name')
593COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
594DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
595 
596COST_FMT=$(printf '$%.2f' "$COST")
597DURATION_SEC=$((DURATION_MS / 1000))
598MINS=$((DURATION_SEC / 60))
599SECS=$((DURATION_SEC % 60))
600 
601echo "[$MODEL] 💰 $COST_FMT | ⏱️ ${MINS}m ${SECS}s"
602```
603 
604```python Python theme={null}
605#!/usr/bin/env python3
606import json, sys
607 
608data = json.load(sys.stdin)
609model = data['model']['display_name']
610cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
611duration_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
612 
613duration_sec = duration_ms // 1000
614mins, secs = duration_sec // 60, duration_sec % 60
615 
616print(f"[{model}] 💰 ${cost:.2f} | ⏱️ {mins}m {secs}s")
617```
618 
619```javascript Node.js theme={null}
620#!/usr/bin/env node
621let input = '';
622process.stdin.on('data', chunk => input += chunk);
623process.stdin.on('end', () => {
624 const data = JSON.parse(input);
625 const model = data.model.display_name;
626 const cost = data.cost?.total_cost_usd || 0;
627 const durationMs = data.cost?.total_duration_ms || 0;
628 
629 const durationSec = Math.floor(durationMs / 1000);
630 const mins = Math.floor(durationSec / 60);
631 const secs = durationSec % 60;
632 
633 console.log(`[${model}] 💰 $${cost.toFixed(2)} | ⏱️ ${mins}m ${secs}s`);
634});
635```
652636 
653637### Display multiple lines
654638 
655639Your script can output multiple lines to create a richer display.
656640 
657<Frame>
658 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-multiline.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=a9d0a2fe8e446d80b1abc46da3f93270" alt="A multi-line status line showing model name, directory, git branch on the first line, and a context usage progress bar with cost and duration on the second line" width="1224" height="262" data-path="images/statusline-multiline.png" />
659</Frame>
641![A multi-line status line showing model name, directory, git branch on the first line, and a context usage progress bar with cost and duration on the second line](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-multiline.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=a9d0a2fe8e446d80b1abc46da3f93270)
660642 
661643This example combines several techniques: threshold-based colors (green under 70%, yellow 70-89%, red 90%+), a progress bar, and git branch info. Each `print` or `echo` statement creates a separate row:
662644 
663<CodeGroup>
664 ```bash Bash theme={null}
665 #!/bin/bash
666 input=$(cat)
667 
668 MODEL=$(echo "$input" | jq -r '.model.display_name')
669 DIR=$(echo "$input" | jq -r '.workspace.current_dir')
670 COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
671 PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
672 DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
673 
674 CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
675 
676 # Pick bar color based on context usage
677 if [ "$PCT" -ge 90 ]; then BAR_COLOR="$RED"
678 elif [ "$PCT" -ge 70 ]; then BAR_COLOR="$YELLOW"
679 else BAR_COLOR="$GREEN"; fi
680 
681 FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
682 printf -v FILL "%${FILLED}s"; printf -v PAD "%${EMPTY}s"
683 BAR="${FILL// /█}${PAD// /░}"
684 
685 MINS=$((DURATION_MS / 60000)); SECS=$(((DURATION_MS % 60000) / 1000))
686 
687 BRANCH=""
688 git rev-parse --git-dir > /dev/null 2>&1 && BRANCH=" | 🌿 $(git branch --show-current 2>/dev/null)"
689 
690 echo -e "${CYAN}[$MODEL]${RESET} 📁 ${DIR##*/}$BRANCH"
691 COST_FMT=$(printf '$%.2f' "$COST")
692 echo -e "${BAR_COLOR}${BAR}${RESET} ${PCT}% | ${YELLOW}${COST_FMT}${RESET} | ⏱️ ${MINS}m ${SECS}s"
693 ```
694 
695 ```python Python theme={null}
696 #!/usr/bin/env python3
697 import json, sys, subprocess, os
698 
699 data = json.load(sys.stdin)
700 model = data['model']['display_name']
701 directory = os.path.basename(data['workspace']['current_dir'])
702 cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
703 pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
704 duration_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
705 
706 CYAN, GREEN, YELLOW, RED, RESET = '\033[36m', '\033[32m', '\033[33m', '\033[31m', '\033[0m'
707 
708 bar_color = RED if pct >= 90 else YELLOW if pct >= 70 else GREEN
709 filled = pct // 10
710 bar = '█' * filled + '░' * (10 - filled)
711 
712 mins, secs = duration_ms // 60000, (duration_ms % 60000) // 1000
713 
714 try:
715 branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True, stderr=subprocess.DEVNULL).strip()
716 branch = f" | 🌿 {branch}" if branch else ""
717 except:
718 branch = ""
719 
720 print(f"{CYAN}[{model}]{RESET} 📁 {directory}{branch}")
721 print(f"{bar_color}{bar}{RESET} {pct}% | {YELLOW}${cost:.2f}{RESET} | ⏱️ {mins}m {secs}s")
722 ```
723 
724 ```javascript Node.js theme={null}
725 #!/usr/bin/env node
726 const { execSync } = require('child_process');
727 const path = require('path');
728 
729 let input = '';
730 process.stdin.on('data', chunk => input += chunk);
731 process.stdin.on('end', () => {
732 const data = JSON.parse(input);
733 const model = data.model.display_name;
734 const dir = path.basename(data.workspace.current_dir);
735 const cost = data.cost?.total_cost_usd || 0;
736 const pct = Math.floor(data.context_window?.used_percentage || 0);
737 const durationMs = data.cost?.total_duration_ms || 0;
738 
739 const CYAN = '\x1b[36m', GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RED = '\x1b[31m', RESET = '\x1b[0m';
740 
741 const barColor = pct >= 90 ? RED : pct >= 70 ? YELLOW : GREEN;
742 const filled = Math.floor(pct / 10);
743 const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
744 
745 const mins = Math.floor(durationMs / 60000);
746 const secs = Math.floor((durationMs % 60000) / 1000);
747 
748 let branch = '';
749 try {
750 branch = execSync('git branch --show-current', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
751 branch = branch ? ` | 🌿 ${branch}` : '';
752 } catch {}
753 
754 console.log(`${CYAN}[${model}]${RESET} 📁 ${dir}${branch}`);
755 console.log(`${barColor}${bar}${RESET} ${pct}% | ${YELLOW}$${cost.toFixed(2)}${RESET} | ⏱️ ${mins}m ${secs}s`);
756 });
757 ```
758</CodeGroup>
645```bash Bash theme={null}
646#!/bin/bash
647input=$(cat)
648 
649MODEL=$(echo "$input" | jq -r '.model.display_name')
650DIR=$(echo "$input" | jq -r '.workspace.current_dir')
651COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
652PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
653DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
654 
655CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
656 
657# Pick bar color based on context usage
658if [ "$PCT" -ge 90 ]; then BAR_COLOR="$RED"
659elif [ "$PCT" -ge 70 ]; then BAR_COLOR="$YELLOW"
660else BAR_COLOR="$GREEN"; fi
661 
662FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
663printf -v FILL "%${FILLED}s"; printf -v PAD "%${EMPTY}s"
664BAR="${FILL// /█}${PAD// /░}"
665 
666MINS=$((DURATION_MS / 60000)); SECS=$(((DURATION_MS % 60000) / 1000))
667 
668BRANCH=""
669git rev-parse --git-dir > /dev/null 2>&1 && BRANCH=" | 🌿 $(git branch --show-current 2>/dev/null)"
670 
671echo -e "${CYAN}[$MODEL]${RESET} 📁 ${DIR##*/}$BRANCH"
672COST_FMT=$(printf '$%.2f' "$COST")
673echo -e "${BAR_COLOR}${BAR}${RESET} ${PCT}% | ${YELLOW}${COST_FMT}${RESET} | ⏱️ ${MINS}m ${SECS}s"
674```
675 
676```python Python theme={null}
677#!/usr/bin/env python3
678import json, sys, subprocess, os
679 
680data = json.load(sys.stdin)
681model = data['model']['display_name']
682directory = os.path.basename(data['workspace']['current_dir'])
683cost = data.get('cost', {}).get('total_cost_usd', 0) or 0
684pct = int(data.get('context_window', {}).get('used_percentage', 0) or 0)
685duration_ms = data.get('cost', {}).get('total_duration_ms', 0) or 0
686 
687CYAN, GREEN, YELLOW, RED, RESET = '\033[36m', '\033[32m', '\033[33m', '\033[31m', '\033[0m'
688 
689bar_color = RED if pct >= 90 else YELLOW if pct >= 70 else GREEN
690filled = pct // 10
691bar = '█' * filled + '░' * (10 - filled)
692 
693mins, secs = duration_ms // 60000, (duration_ms % 60000) // 1000
694 
695try:
696 branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True, stderr=subprocess.DEVNULL).strip()
697 branch = f" | 🌿 {branch}" if branch else ""
698except:
699 branch = ""
700 
701print(f"{CYAN}[{model}]{RESET} 📁 {directory}{branch}")
702print(f"{bar_color}{bar}{RESET} {pct}% | {YELLOW}${cost:.2f}{RESET} | ⏱️ {mins}m {secs}s")
703```
704 
705```javascript Node.js theme={null}
706#!/usr/bin/env node
707const { execSync } = require('child_process');
708const path = require('path');
709 
710let input = '';
711process.stdin.on('data', chunk => input += chunk);
712process.stdin.on('end', () => {
713 const data = JSON.parse(input);
714 const model = data.model.display_name;
715 const dir = path.basename(data.workspace.current_dir);
716 const cost = data.cost?.total_cost_usd || 0;
717 const pct = Math.floor(data.context_window?.used_percentage || 0);
718 const durationMs = data.cost?.total_duration_ms || 0;
719 
720 const CYAN = '\x1b[36m', GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RED = '\x1b[31m', RESET = '\x1b[0m';
721 
722 const barColor = pct >= 90 ? RED : pct >= 70 ? YELLOW : GREEN;
723 const filled = Math.floor(pct / 10);
724 const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
725 
726 const mins = Math.floor(durationMs / 60000);
727 const secs = Math.floor((durationMs % 60000) / 1000);
728 
729 let branch = '';
730 try {
731 branch = execSync('git branch --show-current', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
732 branch = branch ? ` | 🌿 ${branch}` : '';
733 } catch {}
734 
735 console.log(`${CYAN}[${model}]${RESET} 📁 ${dir}${branch}`);
736 console.log(`${barColor}${bar}${RESET} ${pct}% | ${YELLOW}$${cost.toFixed(2)}${RESET} | ⏱️ ${mins}m ${secs}s`);
737});
738```
759739 
760740### Clickable links
761741 
762742This example creates a clickable link to your GitHub repository. Hold Cmd (macOS) or Ctrl (Windows/Linux) and click to open the link in your browser.
763743 
764<Frame>
765 <img src="https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-links.png?fit=max&auto=format&n=HDAmBwgbrZVk0pOt&q=85&s=4778a144a28cb498c99d5fa018bb374a" alt="A status line showing a clickable link to a GitHub repository" width="1224" height="224" data-path="images/statusline-links.png" />
766</Frame>
744![A status line showing a clickable link to a GitHub repository](https://mintcdn.com/claude-code/HDAmBwgbrZVk0pOt/images/statusline-links.png?fit=max\&auto=format\&n=HDAmBwgbrZVk0pOt\&q=85\&s=4778a144a28cb498c99d5fa018bb374a)
767745 
768746Each script gets the git remote URL, converts SSH format to HTTPS, and wraps the repo name in OSC 8 escape codes. The Bash version uses `printf '%b'` which interprets backslash escapes more reliably than `echo -e` across different shells:
769747 
770<CodeGroup>
771 ```bash Bash theme={null}
772 #!/bin/bash
773 input=$(cat)
774 
775 MODEL=$(echo "$input" | jq -r '.model.display_name')
776 
777 # Convert git SSH URL to HTTPS
778 REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/[email protected]:/https:\/\/github.com\//' | sed 's/\.git$//')
779 
780 if [ -n "$REMOTE" ]; then
781 REPO_NAME=$(basename "$REMOTE")
782 # OSC 8 format: \e]8;;URL\a then TEXT then \e]8;;\a
783 # printf %b interprets escape sequences reliably across shells
784 printf '%b' "[$MODEL] 🔗 \e]8;;${REMOTE}\a${REPO_NAME}\e]8;;\a\n"
785 else
786 echo "[$MODEL]"
787 fi
788 ```
789 
790 ```python Python theme={null}
791 #!/usr/bin/env python3
792 import json, sys, subprocess, re, os
793 
794 data = json.load(sys.stdin)
795 model = data['model']['display_name']
796 
797 # Get git remote URL
798 try:
799 remote = subprocess.check_output(
800 ['git', 'remote', 'get-url', 'origin'],
801 stderr=subprocess.DEVNULL, text=True
802 ).strip()
803 # Convert SSH to HTTPS format
804 remote = re.sub(r'^git@github\.com:', 'https://github.com/', remote)
805 remote = re.sub(r'\.git$', '', remote)
806 repo_name = os.path.basename(remote)
807 # OSC 8 escape sequences
808 link = f"\033]8;;{remote}\a{repo_name}\033]8;;\a"
809 print(f"[{model}] 🔗 {link}")
810 except:
811 print(f"[{model}]")
812 ```
813 
814 ```javascript Node.js theme={null}
815 #!/usr/bin/env node
816 const { execSync } = require('child_process');
817 const path = require('path');
818 
819 let input = '';
820 process.stdin.on('data', chunk => input += chunk);
821 process.stdin.on('end', () => {
822 const data = JSON.parse(input);
823 const model = data.model.display_name;
824 
825 try {
826 let remote = execSync('git remote get-url origin', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
827 // Convert SSH to HTTPS format
828 remote = remote.replace(/^git@github\.com:/, 'https://github.com/').replace(/\.git$/, '');
829 const repoName = path.basename(remote);
830 // OSC 8 escape sequences
831 const link = `\x1b]8;;${remote}\x07${repoName}\x1b]8;;\x07`;
832 console.log(`[${model}] 🔗 ${link}`);
833 } catch {
834 console.log(`[${model}]`);
835 }
836 });
837 ```
838</CodeGroup>
748```bash Bash theme={null}
749#!/bin/bash
750input=$(cat)
751 
752MODEL=$(echo "$input" | jq -r '.model.display_name')
753 
754# Convert git SSH URL to HTTPS
755REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/[email protected]:/https:\/\/github.com\//' | sed 's/\.git$//')
756 
757if [ -n "$REMOTE" ]; then
758 REPO_NAME=$(basename "$REMOTE")
759 # OSC 8 format: \e]8;;URL\a then TEXT then \e]8;;\a
760 # printf %b interprets escape sequences reliably across shells
761 printf '%b' "[$MODEL] 🔗 \e]8;;${REMOTE}\a${REPO_NAME}\e]8;;\a\n"
762else
763 echo "[$MODEL]"
764fi
765```
766 
767```python Python theme={null}
768#!/usr/bin/env python3
769import json, sys, subprocess, re, os
770 
771data = json.load(sys.stdin)
772model = data['model']['display_name']
773 
774# Get git remote URL
775try:
776 remote = subprocess.check_output(
777 ['git', 'remote', 'get-url', 'origin'],
778 stderr=subprocess.DEVNULL, text=True
779 ).strip()
780 # Convert SSH to HTTPS format
781 remote = re.sub(r'^git@github\.com:', 'https://github.com/', remote)
782 remote = re.sub(r'\.git$', '', remote)
783 repo_name = os.path.basename(remote)
784 # OSC 8 escape sequences
785 link = f"\033]8;;{remote}\a{repo_name}\033]8;;\a"
786 print(f"[{model}] 🔗 {link}")
787except:
788 print(f"[{model}]")
789```
790 
791```javascript Node.js theme={null}
792#!/usr/bin/env node
793const { execSync } = require('child_process');
794const path = require('path');
795 
796let input = '';
797process.stdin.on('data', chunk => input += chunk);
798process.stdin.on('end', () => {
799 const data = JSON.parse(input);
800 const model = data.model.display_name;
801 
802 try {
803 let remote = execSync('git remote get-url origin', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
804 // Convert SSH to HTTPS format
805 remote = remote.replace(/^git@github\.com:/, 'https://github.com/').replace(/\.git$/, '');
806 const repoName = path.basename(remote);
807 // OSC 8 escape sequences
808 const link = `\x1b]8;;${remote}\x07${repoName}\x1b]8;;\x07`;
809 console.log(`[${model}] 🔗 ${link}`);
810 } catch {
811 console.log(`[${model}]`);
812 }
813});
814```
839815 
840816### Rate limit usage
841817 
from line 819
843819 
844820The `rate_limits` object is only present for claude.ai Pro and Max subscribers, or behind a Claude apps gateway with spend limits, and only after the first API response. Each script handles the absent fields gracefully, and behind a gateway prints `spend: $314.12 / $500`, or `spend: 63%` while the dollar fields are absent:
845821 
846<CodeGroup>
847 ```bash Bash theme={null}
848 #!/bin/bash
849 input=$(cat)
850 
851 MODEL=$(echo "$input" | jq -r '.model.display_name')
852 # "// empty" produces no output when rate_limits is absent
853 FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
854 WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
855 
856 LIMITS=""
857 [ -n "$FIVE_H" ] && LIMITS="5h: $(printf '%.0f' "$FIVE_H")%"
858 [ -n "$WEEK" ] && LIMITS="${LIMITS:+$LIMITS }7d: $(printf '%.0f' "$WEEK")%"
859 
860 # Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
861 SPEND_PCT=$(echo "$input" | jq -r '.rate_limits.spend_limit.used_percentage // empty')
862 SPEND_USD=$(echo "$input" | jq -r '.rate_limits.spend_limit | select(.used_usd != null) | "$\(.used_usd) / $\(.limit_usd)"')
863 [ -n "$SPEND_PCT" ] && LIMITS="${LIMITS:+$LIMITS }spend: ${SPEND_USD:-$(printf '%.0f' "$SPEND_PCT")%}"
864 
865 [ -n "$LIMITS" ] && echo "[$MODEL] | $LIMITS" || echo "[$MODEL]"
866 ```
867 
868 ```python Python theme={null}
869 #!/usr/bin/env python3
870 import json, sys
871 
872 data = json.load(sys.stdin)
873 model = data['model']['display_name']
874 
875 parts = []
876 rate = data.get('rate_limits', {})
877 five_h = rate.get('five_hour', {}).get('used_percentage')
878 week = rate.get('seven_day', {}).get('used_percentage')
879 
880 if five_h is not None:
881 parts.append(f"5h: {five_h:.0f}%")
882 if week is not None:
883 parts.append(f"7d: {week:.0f}%")
884 
885 # Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
886 spend = rate.get('spend_limit', {})
887 if spend.get('used_percentage') is not None:
888 if spend.get('used_usd') is not None:
889 parts.append(f"spend: ${spend['used_usd']} / ${spend['limit_usd']}")
890 else:
891 parts.append(f"spend: {spend['used_percentage']:.0f}%")
892 
893 if parts:
894 print(f"[{model}] | {' '.join(parts)}")
895 else:
896 print(f"[{model}]")
897 ```
898 
899 ```javascript Node.js theme={null}
900 #!/usr/bin/env node
901 let input = '';
902 process.stdin.on('data', chunk => input += chunk);
903 process.stdin.on('end', () => {
904 const data = JSON.parse(input);
905 const model = data.model.display_name;
906 
907 const parts = [];
908 const fiveH = data.rate_limits?.five_hour?.used_percentage;
909 const week = data.rate_limits?.seven_day?.used_percentage;
910 
911 if (fiveH != null) parts.push(`5h: ${Math.round(fiveH)}%`);
912 if (week != null) parts.push(`7d: ${Math.round(week)}%`);
913 
914 // Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
915 const spend = data.rate_limits?.spend_limit;
916 if (spend?.used_percentage != null) {
917 parts.push(spend.used_usd != null
918 ? `spend: $${spend.used_usd} / $${spend.limit_usd}`
919 : `spend: ${Math.round(spend.used_percentage)}%`);
920 }
921 
922 console.log(parts.length ? `[${model}] | ${parts.join(' ')}` : `[${model}]`);
923 });
924 ```
925</CodeGroup>
822```bash Bash theme={null}
823#!/bin/bash
824input=$(cat)
825 
826MODEL=$(echo "$input" | jq -r '.model.display_name')
827# "// empty" produces no output when rate_limits is absent
828FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
829WEEK=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
830 
831LIMITS=""
832[ -n "$FIVE_H" ] && LIMITS="5h: $(printf '%.0f' "$FIVE_H")%"
833[ -n "$WEEK" ] && LIMITS="${LIMITS:+$LIMITS }7d: $(printf '%.0f' "$WEEK")%"
834 
835# Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
836SPEND_PCT=$(echo "$input" | jq -r '.rate_limits.spend_limit.used_percentage // empty')
837SPEND_USD=$(echo "$input" | jq -r '.rate_limits.spend_limit | select(.used_usd != null) | "$\(.used_usd) / $\(.limit_usd)"')
838[ -n "$SPEND_PCT" ] && LIMITS="${LIMITS:+$LIMITS }spend: ${SPEND_USD:-$(printf '%.0f' "$SPEND_PCT")%}"
839 
840[ -n "$LIMITS" ] && echo "[$MODEL] | $LIMITS" || echo "[$MODEL]"
841```
842 
843```python Python theme={null}
844#!/usr/bin/env python3
845import json, sys
846 
847data = json.load(sys.stdin)
848model = data['model']['display_name']
849 
850parts = []
851rate = data.get('rate_limits', {})
852five_h = rate.get('five_hour', {}).get('used_percentage')
853week = rate.get('seven_day', {}).get('used_percentage')
854 
855if five_h is not None:
856 parts.append(f"5h: {five_h:.0f}%")
857if week is not None:
858 parts.append(f"7d: {week:.0f}%")
859 
860# Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
861spend = rate.get('spend_limit', {})
862if spend.get('used_percentage') is not None:
863 if spend.get('used_usd') is not None:
864 parts.append(f"spend: ${spend['used_usd']} / ${spend['limit_usd']}")
865 else:
866 parts.append(f"spend: {spend['used_percentage']:.0f}%")
867 
868if parts:
869 print(f"[{model}] | {' '.join(parts)}")
870else:
871 print(f"[{model}]")
872```
873 
874```javascript Node.js theme={null}
875#!/usr/bin/env node
876let input = '';
877process.stdin.on('data', chunk => input += chunk);
878process.stdin.on('end', () => {
879 const data = JSON.parse(input);
880 const model = data.model.display_name;
881 
882 const parts = [];
883 const fiveH = data.rate_limits?.five_hour?.used_percentage;
884 const week = data.rate_limits?.seven_day?.used_percentage;
885 
886 if (fiveH != null) parts.push(`5h: ${Math.round(fiveH)}%`);
887 if (week != null) parts.push(`7d: ${Math.round(week)}%`);
888 
889 // Behind a Claude apps gateway: dollars when the gateway reports them, else the percentage
890 const spend = data.rate_limits?.spend_limit;
891 if (spend?.used_percentage != null) {
892 parts.push(spend.used_usd != null
893 ? `spend: $${spend.used_usd} / $${spend.limit_usd}`
894 : `spend: ${Math.round(spend.used_percentage)}%`);
895 }
896 
897 console.log(parts.length ? `[${model}] | ${parts.join(' ')}` : `[${model}]`);
898});
899```
926900 
927901### Cache expensive operations
928902 
from line 906
932906 
933907Each script checks if the cache file is missing or older than 5 seconds before running git commands:
934908 
935<CodeGroup>
936 ```bash Bash theme={null}
937 #!/bin/bash
938 input=$(cat)
939 
940 MODEL=$(echo "$input" | jq -r '.model.display_name')
941 DIR=$(echo "$input" | jq -r '.workspace.current_dir')
942 SESSION_ID=$(echo "$input" | jq -r '.session_id')
943 
944 CACHE_FILE="/tmp/statusline-git-cache-$SESSION_ID"
945 CACHE_MAX_AGE=5 # seconds
946 
947 cache_is_stale() {
948 [ ! -f "$CACHE_FILE" ] || \
949 # stat -c %Y (Linux) or stat -f %m (macOS) prints the file's last-modified
950 # time. The Linux form must run first: on Linux, the macOS form prints a
951 # filesystem report to stdout before failing, and that output would be
952 # captured by the command substitution and break the arithmetic.
953 [ $(($(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0))) -gt $CACHE_MAX_AGE ]
909```bash Bash theme={null}
910#!/bin/bash
911input=$(cat)
912 
913MODEL=$(echo "$input" | jq -r '.model.display_name')
914DIR=$(echo "$input" | jq -r '.workspace.current_dir')
915SESSION_ID=$(echo "$input" | jq -r '.session_id')
916 
917CACHE_FILE="/tmp/statusline-git-cache-$SESSION_ID"
918CACHE_MAX_AGE=5 # seconds
919 
920cache_is_stale() {
921 [ ! -f "$CACHE_FILE" ] || \
922 # stat -c %Y (Linux) or stat -f %m (macOS) prints the file's last-modified
923 # time. The Linux form must run first: on Linux, the macOS form prints a
924 # filesystem report to stdout before failing, and that output would be
925 # captured by the command substitution and break the arithmetic.
926 [ $(($(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0))) -gt $CACHE_MAX_AGE ]
927}
928 
929if cache_is_stale; then
930 if git rev-parse --git-dir > /dev/null 2>&1; then
931 BRANCH=$(git branch --show-current 2>/dev/null)
932 STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
933 MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
934 echo "$BRANCH|$STAGED|$MODIFIED" > "$CACHE_FILE"
935 else
936 echo "||" > "$CACHE_FILE"
937 fi
938fi
939 
940IFS='|' read -r BRANCH STAGED MODIFIED < "$CACHE_FILE"
941 
942if [ -n "$BRANCH" ]; then
943 echo "[$MODEL] 📁 ${DIR##*/} | 🌿 $BRANCH +$STAGED ~$MODIFIED"
944else
945 echo "[$MODEL] 📁 ${DIR##*/}"
946fi
947```
948 
949```python Python theme={null}
950#!/usr/bin/env python3
951import json, sys, subprocess, os, time
952 
953data = json.load(sys.stdin)
954model = data['model']['display_name']
955directory = os.path.basename(data['workspace']['current_dir'])
956session_id = data['session_id']
957 
958CACHE_FILE = f"/tmp/statusline-git-cache-{session_id}"
959CACHE_MAX_AGE = 5 # seconds
960 
961def cache_is_stale():
962 if not os.path.exists(CACHE_FILE):
963 return True
964 return time.time() - os.path.getmtime(CACHE_FILE) > CACHE_MAX_AGE
965 
966if cache_is_stale():
967 try:
968 subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL)
969 branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True).strip()
970 staged = subprocess.check_output(['git', 'diff', '--cached', '--numstat'], text=True).strip()
971 modified = subprocess.check_output(['git', 'diff', '--numstat'], text=True).strip()
972 staged_count = len(staged.split('\n')) if staged else 0
973 modified_count = len(modified.split('\n')) if modified else 0
974 with open(CACHE_FILE, 'w') as f:
975 f.write(f"{branch}|{staged_count}|{modified_count}")
976 except:
977 with open(CACHE_FILE, 'w') as f:
978 f.write("||")
979 
980with open(CACHE_FILE) as f:
981 branch, staged, modified = f.read().strip().split('|')
982 
983if branch:
984 print(f"[{model}] 📁 {directory} | 🌿 {branch} +{staged} ~{modified}")
985else:
986 print(f"[{model}] 📁 {directory}")
987```
988 
989```javascript Node.js theme={null}
990#!/usr/bin/env node
991const { execSync } = require('child_process');
992const fs = require('fs');
993const path = require('path');
994 
995let input = '';
996process.stdin.on('data', chunk => input += chunk);
997process.stdin.on('end', () => {
998 const data = JSON.parse(input);
999 const model = data.model.display_name;
1000 const dir = path.basename(data.workspace.current_dir);
1001 const sessionId = data.session_id;
1002 
1003 const CACHE_FILE = `/tmp/statusline-git-cache-${sessionId}`;
1004 const CACHE_MAX_AGE = 5; // seconds
1005 
1006 const cacheIsStale = () => {
1007 if (!fs.existsSync(CACHE_FILE)) return true;
1008 return (Date.now() / 1000) - fs.statSync(CACHE_FILE).mtimeMs / 1000 > CACHE_MAX_AGE;
1009 };
1010 
1011 if (cacheIsStale()) {
1012 try {
1013 execSync('git rev-parse --git-dir', { stdio: 'ignore' });
1014 const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
1015 const staged = execSync('git diff --cached --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
1016 const modified = execSync('git diff --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
1017 fs.writeFileSync(CACHE_FILE, `${branch}|${staged}|${modified}`);
1018 } catch {
1019 fs.writeFileSync(CACHE_FILE, '||');
1020 }
1021 }
1022 
1023 const [branch, staged, modified] = fs.readFileSync(CACHE_FILE, 'utf8').trim().split('|');
1024 
1025 if (branch) {
1026 console.log(`[${model}] 📁 ${dir} | 🌿 ${branch} +${staged} ~${modified}`);
1027 } else {
1028 console.log(`[${model}] 📁 ${dir}`);
1029 }
1030});
1031```
1032 
1033### Windows configuration
1034 
1035On Windows, Claude Code runs status line commands through Git Bash when Git Bash is installed, or through PowerShell when Git Bash is absent.
1036 
1037Git Bash treats unquoted backslashes as escape characters, so a Windows-style path such as `C:\Users\username\script.mjs` reaches the script runner with its separators removed and the command fails without a visible error. Write file paths in the `command` string with forward slashes, as shown in the examples below. The `~` shorthand also works and expands to your Windows home directory.
1038 
1039To run a PowerShell script as your status line, invoke it via `powershell`. This works whether Claude Code routes the command through Git Bash or PowerShell:
1040 
1041```json settings.json theme={null}
1042{
1043 "statusLine": {
1044 "type": "command",
1045 "command": "powershell -NoProfile -File C:/Users/username/.claude/statusline.ps1"
9541046 }
955 
956 if cache_is_stale; then
957 if git rev-parse --git-dir > /dev/null 2>&1; then
958 BRANCH=$(git branch --show-current 2>/dev/null)
959 STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
960 MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
961 echo "$BRANCH|$STAGED|$MODIFIED" > "$CACHE_FILE"
962 else
963 echo "||" > "$CACHE_FILE"
964 fi
965 fi
966 
967 IFS='|' read -r BRANCH STAGED MODIFIED < "$CACHE_FILE"
968 
969 if [ -n "$BRANCH" ]; then
970 echo "[$MODEL] 📁 ${DIR##*/} | 🌿 $BRANCH +$STAGED ~$MODIFIED"
971 else
972 echo "[$MODEL] 📁 ${DIR##*/}"
973 fi
974 ```
975 
976 ```python Python theme={null}
977 #!/usr/bin/env python3
978 import json, sys, subprocess, os, time
979 
980 data = json.load(sys.stdin)
981 model = data['model']['display_name']
982 directory = os.path.basename(data['workspace']['current_dir'])
983 session_id = data['session_id']
984 
985 CACHE_FILE = f"/tmp/statusline-git-cache-{session_id}"
986 CACHE_MAX_AGE = 5 # seconds
987 
988 def cache_is_stale():
989 if not os.path.exists(CACHE_FILE):
990 return True
991 return time.time() - os.path.getmtime(CACHE_FILE) > CACHE_MAX_AGE
992 
993 if cache_is_stale():
994 try:
995 subprocess.check_output(['git', 'rev-parse', '--git-dir'], stderr=subprocess.DEVNULL)
996 branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True).strip()
997 staged = subprocess.check_output(['git', 'diff', '--cached', '--numstat'], text=True).strip()
998 modified = subprocess.check_output(['git', 'diff', '--numstat'], text=True).strip()
999 staged_count = len(staged.split('\n')) if staged else 0
1000 modified_count = len(modified.split('\n')) if modified else 0
1001 with open(CACHE_FILE, 'w') as f:
1002 f.write(f"{branch}|{staged_count}|{modified_count}")
1003 except:
1004 with open(CACHE_FILE, 'w') as f:
1005 f.write("||")
1006 
1007 with open(CACHE_FILE) as f:
1008 branch, staged, modified = f.read().strip().split('|')
1009 
1010 if branch:
1011 print(f"[{model}] 📁 {directory} | 🌿 {branch} +{staged} ~{modified}")
1012 else:
1013 print(f"[{model}] 📁 {directory}")
1014 ```
1015 
1016 ```javascript Node.js theme={null}
1017 #!/usr/bin/env node
1018 const { execSync } = require('child_process');
1019 const fs = require('fs');
1020 const path = require('path');
1021 
1022 let input = '';
1023 process.stdin.on('data', chunk => input += chunk);
1024 process.stdin.on('end', () => {
1025 const data = JSON.parse(input);
1026 const model = data.model.display_name;
1027 const dir = path.basename(data.workspace.current_dir);
1028 const sessionId = data.session_id;
1029 
1030 const CACHE_FILE = `/tmp/statusline-git-cache-${sessionId}`;
1031 const CACHE_MAX_AGE = 5; // seconds
1032 
1033 const cacheIsStale = () => {
1034 if (!fs.existsSync(CACHE_FILE)) return true;
1035 return (Date.now() / 1000) - fs.statSync(CACHE_FILE).mtimeMs / 1000 > CACHE_MAX_AGE;
1036 };
1037 
1038 if (cacheIsStale()) {
1039 try {
1040 execSync('git rev-parse --git-dir', { stdio: 'ignore' });
1041 const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
1042 const staged = execSync('git diff --cached --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
1043 const modified = execSync('git diff --numstat', { encoding: 'utf8' }).trim().split('\n').filter(Boolean).length;
1044 fs.writeFileSync(CACHE_FILE, `${branch}|${staged}|${modified}`);
1045 } catch {
1046 fs.writeFileSync(CACHE_FILE, '||');
1047 }
1048 }
1049 
1050 const [branch, staged, modified] = fs.readFileSync(CACHE_FILE, 'utf8').trim().split('|');
1051 
1052 if (branch) {
1053 console.log(`[${model}] 📁 ${dir} | 🌿 ${branch} +${staged} ~${modified}`);
1054 } else {
1055 console.log(`[${model}] 📁 ${dir}`);
1056 }
1057 });
1058 ```
1059</CodeGroup>
1060 
1061### Windows configuration
1062 
1063On Windows, Claude Code runs status line commands through Git Bash when Git Bash is installed, or through PowerShell when Git Bash is absent.
1064 
1065Git Bash treats unquoted backslashes as escape characters, so a Windows-style path such as `C:\Users\username\script.mjs` reaches the script runner with its separators removed and the command fails without a visible error. Write file paths in the `command` string with forward slashes, as shown in the examples below. The `~` shorthand also works and expands to your Windows home directory.
1066 
1067To run a PowerShell script as your status line, invoke it via `powershell`. This works whether Claude Code routes the command through Git Bash or PowerShell:
1068 
1069<CodeGroup>
1070 ```json settings.json theme={null}
1071 {
1072 "statusLine": {
1073 "type": "command",
1074 "command": "powershell -NoProfile -File C:/Users/username/.claude/statusline.ps1"
1075 }
1047}
1048```
1049 
1050```powershell statusline.ps1 theme={null}
1051$input_json = $input | Out-String | ConvertFrom-Json
1052$cwd = $input_json.cwd
1053$model = $input_json.model.display_name
1054$used = $input_json.context_window.used_percentage
1055$dirname = Split-Path $cwd -Leaf
1056 
1057if ($used) {
1058 Write-Host "$dirname [$model] ctx: $used%"
1059} else {
1060 Write-Host "$dirname [$model]"
1061}
1062```
1063 
1064Or, when Git Bash is installed, run a Bash script directly:
1065 
1066```json settings.json theme={null}
1067{
1068 "statusLine": {
1069 "type": "command",
1070 "command": "~/.claude/statusline.sh"
10761071 }
1077 ```
1078 
1079 ```powershell statusline.ps1 theme={null}
1080 $input_json = $input | Out-String | ConvertFrom-Json
1081 $cwd = $input_json.cwd
1082 $model = $input_json.model.display_name
1083 $used = $input_json.context_window.used_percentage
1084 $dirname = Split-Path $cwd -Leaf
1085 
1086 if ($used) {
1087 Write-Host "$dirname [$model] ctx: $used%"
1088 } else {
1089 Write-Host "$dirname [$model]"
1090 }
1091 ```
1092</CodeGroup>
1093 
1094Or, when Git Bash is installed, run a Bash script directly:
1095 
1096<CodeGroup>
1097 ```json settings.json theme={null}
1098 {
1099 "statusLine": {
1100 "type": "command",
1101 "command": "~/.claude/statusline.sh"
1102 }
1103 }
1104 ```
1105 
1106 ```bash statusline.sh theme={null}
1107 #!/usr/bin/env bash
1108 input=$(cat)
1109 cwd=$(echo "$input" | grep -o '"cwd":"[^"]*"' | cut -d'"' -f4)
1110 model=$(echo "$input" | grep -o '"display_name":"[^"]*"' | cut -d'"' -f4)
1111 dirname="${cwd##*[/\\]}"
1112 echo "$dirname [$model]"
1113 ```
1114</CodeGroup>
1072}
1073```
1074 
1075```bash statusline.sh theme={null}
1076#!/usr/bin/env bash
1077input=$(cat)
1078cwd=$(echo "$input" | grep -o '"cwd":"[^"]*"' | cut -d'"' -f4)
1079model=$(echo "$input" | grep -o '"display_name":"[^"]*"' | cut -d'"' -f4)
1080dirname="${cwd##*[/\\]}"
1081echo "$dirname [$model]"
1082```
11151083 
11161084## Subagent status lines
11171085 
11181086 
Feedback