A Node.js-based custom status line for Claude Code that displays model information, git branch status, session cost, and a 5-hour countdown timer.
This status line provides real-time information about your Claude Code session:
- Model Display: Shows current model (Sonnet π€ or Opus π§ ) with distinctive emojis
- Git Branch: Shows current git branch (πΏ branch-name) or "π No Git" if not in a repository
- Session Timer: Displays countdown timer (β³ H:MM) for 5-hour session blocks
- Session Cost: Shows current session cost (π° $X.XX) when available
Example Output:
π€ Sonnet | πΏ main | β³ 4:32
π§ Opus | π No Git | β³ 2:15
π€ Sonnet | πΏ dev | β³ 3:45 | π° $0.054
π§ Opus | πΏ main | β³ 1:30 | π° $0.125
π€ Sonnet | πΏ main | β³ 2:45 | π° $0.287
statusline.js- Main status line scriptsettings.json.example- Example Claude Code settings configurationREADME.md- This comprehensive documentation
- Node.js (any recent version)
- Claude Code with status line support
- Git (optional, for branch display)
Clone this repository and run the installer:
Windows (Command Prompt or PowerShell):
git clone https://github.com/yourusername/claude-code-statusline.git && cd claude-code-statusline && install.batmacOS/Linux:
git clone https://github.com/yourusername/claude-code-statusline.git && cd claude-code-statusline && chmod +x install.sh && ./install.shCross-Platform (Node.js):
git clone https://github.com/yourusername/claude-code-statusline.git && cd claude-code-statusline && node install.js-
Copy the Script
# Windows copy statusline.js %USERPROFILE%\.claude\statusline\statusline.js # macOS/Linux cp statusline.js ~/.claude/statusline/statusline.js
-
Configure Claude Code
Add to
~/.claude/settings.json(or%USERPROFILE%\.claude\settings.jsonon Windows):{ "statusLine": { "type": "command", "command": "node \"~/.claude/statusline/statusline.js\"", "padding": 0 } }Note: On Windows, use the full path like
"C:\\Users\\YourName\\.claude\\statusline\\statusline.js" -
Restart Claude Code
The status line will appear at the bottom of your Claude Code interface after restarting.
The status line script receives JSON data via stdin from Claude Code and outputs a formatted string to stdout:
JSON Input β statusline.js β Formatted Output β Claude Code UI
Claude Code provides contextual information as JSON:
{
"model": {
"id": "claude-sonnet-4-20250514",
"display_name": "Sonnet"
},
"workspace": {
"current_dir": "/path/to/project",
"project_dir": "/path/to/project"
},
"session_id": "abc123...",
"transcript_path": "/path/to/transcript.json"
}const modelId = data.model.id;
let modelEmoji = 'π€'; // Default for Sonnet
if (modelId.toLowerCase().includes('opus')) {
modelEmoji = 'π§ ';
}
const modelInfo = `${modelEmoji} ${data.model.display_name}`;const gitDir = path.join(currentDir, '.git');
if (fs.existsSync(gitDir)) {
const headContent = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
if (headContent.startsWith('ref: refs/heads/')) {
const branchName = headContent.replace('ref: refs/heads/', '');
return `πΏ ${branchName}`;
}
}
return 'π No Git';The timer implements scheduled 5-hour session blocks that count down continuously:
// Calculate which block we should be in
const blockNumber = Math.floor(elapsed / fiveHours);
const scheduledBlockStart = new Date(currentBlockStart.getTime() + (blockNumber * fiveHours));
const nextBlockStart = new Date(scheduledBlockStart.getTime() + fiveHours);
const timeRemaining = Math.max(0, nextBlockStart - now);The timer operates on scheduled 5-hour blocks, not activity-based timing:
- Block 1: Starts when first used (e.g., 10:00 AM - 3:00 PM)
- Block 2: Automatically starts at 3:00 PM - 8:00 PM
- Block 3: Automatically starts at 8:00 PM - 1:00 AM
- And so on...
- Timer counts down regardless of user activity
- Shows remaining time in current block
- Format:
β³ H:MM(hours:minutes)
- Blocks start automatically when previous block ends
- No gap between blocks during continuous use
- Timer only resets if user is completely inactive for an entire 5-hour block
- Partial activity in a block maintains the schedule
User works 10:00 AM - 6:00 PM straight
Block 1: 10:00 AM β 3:00 PM
10:00 AM: β³ 5:00 (start)
2:00 PM: β³ 1:00
3:00 PM: β³ 0:00 (Block 1 ends)
Block 2: 3:00 PM β 8:00 PM
3:00 PM: β³ 5:00 (Block 2 starts immediately)
6:00 PM: β³ 2:00 (still working)
User works 10:00 AM - 3:00 PM, breaks until 7:00 PM
Block 1: 10:00 AM β 3:00 PM
3:00 PM: β³ 0:00 (Block 1 ends, user stops)
Block 2: 3:00 PM β 8:00 PM (scheduled but user inactive)
7:00 PM: β³ 1:00 (user returns - only 1 hour left!)
8:00 PM: β³ 0:00 (Block 2 ends)
Block 3: 8:00 PM β 1:00 AM
8:00 PM: β³ 5:00 (Block 3 starts)
User works 10:00 AM - 3:00 PM, then completely inactive until 10:00 PM
Block 1: 10:00 AM β 3:00 PM (active)
Block 2: 3:00 PM β 8:00 PM (completely inactive)
10:00 PM: β³ 5:00 (NEW Block 1 starts - schedule reset)
Session state is stored in ~/.claude-session-time:
{
"currentBlockStart": "2025-08-19T10:00:00.000Z",
"lastActivity": "2025-08-19T14:32:15.123Z",
"currentScheduledBlock": 0
}Fields:
currentBlockStart: When the original Block 1 startedlastActivity: Last time the status line was accessedcurrentScheduledBlock: Which block the user was last active in
To change model emojis, modify the model detection logic:
let modelEmoji = 'π€'; // Default for Sonnet
if (modelId.toLowerCase().includes('opus')) {
modelEmoji = 'π§ '; // Opus emoji
}
// Add more models:
if (modelId.toLowerCase().includes('haiku')) {
modelEmoji = 'β‘'; // Haiku emoji
}To customize git branch formatting:
// Current format: πΏ branch-name
return `πΏ ${branchName}`;
// Alternative formats:
return `[${branchName}]`; // [main]
return `git:${branchName}`; // git:main
return `${branchName} π`; // main πTo change timer format:
// Current format: β³ H:MM
return `β³ ${hours}:${minutes.toString().padStart(2, '0')}`;
// Alternative formats:
return `${hours}h ${minutes}m remaining`; // 4h 32m remaining
return `[${hours}:${minutes.toString().padStart(2, '0')}]`; // [4:32]
return `Time: ${hours}:${minutes.toString().padStart(2, '0')}`; // Time: 4:32To change from 5-hour blocks to different durations:
// Current: 5 hours
const fiveHours = 5 * 60 * 60 * 1000;
// Alternative durations:
const threeHours = 3 * 60 * 60 * 1000; // 3-hour blocks
const oneHour = 1 * 60 * 60 * 1000; // 1-hour blocks
const thirtyMinutes = 30 * 60 * 1000; // 30-minute blocks- Check Claude Code restart: Status line only loads after restart
- Verify script path: Ensure the path in settings.json is correct and absolute
- Test script manually:
Should output:
echo '{"model":{"id":"claude-sonnet-4","display_name":"Sonnet"},"workspace":{"current_dir":"."}}' | node statusline.js
π€ Sonnet | πΏ main | β³ 5:00
- Check session file:
~/.claude-session-timeshould exist and contain valid JSON - Reset session: Delete
~/.claude-session-timeto start fresh - Verify system time: Ensure your system clock is correct
- Verify git repository: Ensure you're in a valid git repository
- Check .git/HEAD: File should exist and contain branch reference
- Test git command:
git rev-parse --abbrev-ref HEADshould show branch name
- Check Node.js: Ensure Node.js is installed and accessible
- Verify file permissions: Script should be readable
- Check logs: Run Claude Code with debug flags to see error messages
Linux/macOS:
chmod +x statusline.js
chmod 644 ~/.claude/settings.jsonWindows:
- Ensure Node.js is in your PATH
- Use full paths with proper escaping in settings.json
You can create different status lines for different projects by using project-specific settings:
Project settings: .claude/settings.json (in project root)
{
"statusLine": {
"type": "command",
"command": "node \"/path/to/project-specific-statusline.js\""
}
}# In containerized environment
"command": "docker exec container-name node /app/statusline.js"# For SSH/remote development
"command": "ssh remote-host 'node /remote/path/statusline.js'"The status line updates frequently, so consider:
-
Caching expensive operations:
let cachedBranch = null; let lastBranchCheck = 0; function getGitBranch(currentDir) { const now = Date.now(); if (now - lastBranchCheck < 5000 && cachedBranch) { return cachedBranch; // Use cache for 5 seconds } // ... actual branch detection lastBranchCheck = now; cachedBranch = result; return result; }
-
Minimal file I/O: Current implementation is already optimized
-
Error handling: All errors fall back to default values
- File permissions: Session file is created in user's home directory
- No network access: Script operates entirely locally
- Input validation: JSON input is parsed safely with try/catch
- Path traversal: Uses Node.js path.join() for safe path handling
To extend or modify the status line:
- Fork the implementation
- Modify
statusline.jsfor your needs - Test thoroughly with different scenarios
- Update documentation if adding new features
-
Test with mock data:
echo '{"model":{"id":"test","display_name":"Test"},"workspace":{"current_dir":"."}}' | node statusline.js
-
Debug session state:
cat ~/.claude-session-time | jq .
-
Monitor performance:
time echo '{}' | node statusline.js
This implementation is provided as-is for educational and personal use. Modify and distribute freely.
- Added session cost display support (π° $X.XX)
- Added token usage display (π X.XK tokens)
- Shows both cost AND tokens when both are available
- Smart cost formatting based on amount
- Multiple cost field detection (cost, usage, session, tokens)
- Graceful fallback when cost info unavailable
- Initial implementation
- Model detection with emojis (Sonnet π€, Opus π§ )
- Git branch detection with πΏ emoji
- 5-hour session countdown timer with β³ emoji
- Scheduled block system with automatic progression
- Session persistence in
~/.claude-session-time - Comprehensive error handling and fallbacks
- Format:
Model | Git | Timer(e.g.,π€ Sonnet | πΏ main | β³ 4:32)
Last updated: 2025-08-20