claude-best-statusline 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/bin/install.js +70 -0
- package/install.ps1 +112 -0
- package/install.sh +150 -0
- package/package.json +21 -0
- package/preview.png +0 -0
- package/statusline.js +318 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 [Your Name]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Claude Code Enhanced Statusline
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A real-time statusline for [Claude Code](https://github.com/anthropics/claude-code) that shows your current directory, model, context window usage, and session token limits at a glance. One script, zero config. Auto-detects subscription vs API key.
|
|
6
|
+
|
|
7
|
+
## Why This Statusline
|
|
8
|
+
|
|
9
|
+
There are only two things that really matter when vibe-coding: **Context Window** and **Usage** left in your session. Context matters because models have been shown to perform worse as context grows (context rot), and usage matters so you can squeeze every last token out of your session instead of leaving value on the table. This statusline keeps both front and center so you can focus on building.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- [Claude Code](https://github.com/anthropics/claude-code) installed
|
|
14
|
+
- Node.js (comes with Claude Code)
|
|
15
|
+
- Authenticated Claude account (for usage tracking on subscription plans)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Quick Install (Recommended)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx claude-best-statusline
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or with bun:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bunx claude-best-statusline
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Restart Claude Code or start a new session.
|
|
32
|
+
|
|
33
|
+
### Clone & Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/TahaSabir0/claude-statusline.git
|
|
37
|
+
cd claude-statusline
|
|
38
|
+
|
|
39
|
+
# macOS / Linux
|
|
40
|
+
./install.sh
|
|
41
|
+
|
|
42
|
+
# Windows (PowerShell)
|
|
43
|
+
./install.ps1
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Manual Install
|
|
47
|
+
|
|
48
|
+
1. **Download the script:**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -o ~/.claude/hooks/statusline.js https://raw.githubusercontent.com/TahaSabir0/claude-statusline/main/statusline.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. **Make it executable:**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
chmod +x ~/.claude/hooks/statusline.js
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
3. **Update Claude Code settings:**
|
|
61
|
+
|
|
62
|
+
Edit `~/.claude/settings.json` and add/modify the `statusLine` section:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"statusLine": {
|
|
67
|
+
"type": "command",
|
|
68
|
+
"command": "node ~/.claude/hooks/statusline.js"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
4. **Restart Claude Code** or start a new session.
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
- **Context Usage**: Visual bar showing token usage (green → yellow → orange → red)
|
|
78
|
+
- **API Usage**: Real-time 5-hour session limit tracking with countdown timer (subscription users)
|
|
79
|
+
- **Current Directory**: Shows your working directory
|
|
80
|
+
- **Model Name**: Displays which Claude model you're using (Opus, Sonnet, Haiku)
|
|
81
|
+
- **Auto-Detection**: Detects API key vs subscription and adapts automatically
|
|
82
|
+
- **Adaptive Performance**: Fast after first prompt (1.2s vs 1.5s)
|
|
83
|
+
- **Smart Caching**: Shares usage data across sessions, fallback on API timeout
|
|
84
|
+
|
|
85
|
+
**Color Coding:**
|
|
86
|
+
|
|
87
|
+
- 🟢 Green: < 50% usage
|
|
88
|
+
- 🟡 Yellow: 50-75% usage
|
|
89
|
+
- 🟠 Orange: 75-90% usage
|
|
90
|
+
- 🔴 Red (blinking): > 90% usage
|
|
91
|
+
|
|
92
|
+
## How It Works
|
|
93
|
+
|
|
94
|
+
### Adaptive Timing
|
|
95
|
+
|
|
96
|
+
- **First prompt**: Uses 1500ms timeout (cold start, OAuth validation)
|
|
97
|
+
- **Subsequent prompts**: Uses 1200ms timeout (faster, connection reused)
|
|
98
|
+
- **Result**: Smooth experience after initial setup
|
|
99
|
+
|
|
100
|
+
### Caching System
|
|
101
|
+
|
|
102
|
+
- Usage data is cached for 30 seconds in `~/.claude/cache/usage-cache.json`
|
|
103
|
+
- All sessions share the same cache
|
|
104
|
+
- If API call times out, shows cached data (no lag)
|
|
105
|
+
|
|
106
|
+
### API Usage
|
|
107
|
+
|
|
108
|
+
- Fetches from `https://api.anthropic.com/api/oauth/usage`
|
|
109
|
+
- Tracks 5-hour session limits
|
|
110
|
+
- Shows percentage used + time until reset
|
|
111
|
+
- Fails gracefully (no statusline breakage)
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
116
|
+
|
|
117
|
+
## Credits
|
|
118
|
+
|
|
119
|
+
Created by [@TahaSabir0](https://github.com/TahaSabir0)
|
|
120
|
+
|
|
121
|
+
Built for the [Claude Code](https://github.com/anthropics/claude-code) community.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
**Star ⭐ this repo if you find it useful!**
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
8
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
9
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
10
|
+
const scriptDest = path.join(hooksDir, 'statusline.js');
|
|
11
|
+
const scriptSrc = path.join(__dirname, '..', 'statusline.js');
|
|
12
|
+
|
|
13
|
+
const green = '\x1b[32m';
|
|
14
|
+
const red = '\x1b[31m';
|
|
15
|
+
const yellow = '\x1b[33m';
|
|
16
|
+
const cyan = '\x1b[36m';
|
|
17
|
+
const reset = '\x1b[0m';
|
|
18
|
+
|
|
19
|
+
console.log(`${cyan}======================================${reset}`);
|
|
20
|
+
console.log(`${cyan} Claude Code Statusline Installer${reset}`);
|
|
21
|
+
console.log(`${cyan}======================================${reset}\n`);
|
|
22
|
+
|
|
23
|
+
// Check Claude Code is installed
|
|
24
|
+
if (!fs.existsSync(claudeDir)) {
|
|
25
|
+
console.log(`${red}Error: Claude Code not found!${reset}`);
|
|
26
|
+
console.log('Please install Claude Code first: https://github.com/anthropics/claude-code');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Create hooks directory
|
|
31
|
+
if (!fs.existsSync(hooksDir)) {
|
|
32
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Copy statusline script
|
|
36
|
+
console.log(`${yellow}Installing statusline...${reset}`);
|
|
37
|
+
fs.copyFileSync(scriptSrc, scriptDest);
|
|
38
|
+
fs.chmodSync(scriptDest, 0o755);
|
|
39
|
+
console.log(`${green}✓ Installed statusline.js${reset}`);
|
|
40
|
+
|
|
41
|
+
// Update settings.json
|
|
42
|
+
console.log(`${yellow}Updating settings...${reset}`);
|
|
43
|
+
|
|
44
|
+
let settings = {};
|
|
45
|
+
if (fs.existsSync(settingsFile)) {
|
|
46
|
+
// Backup existing settings
|
|
47
|
+
const backup = `${settingsFile}.backup.${Date.now()}`;
|
|
48
|
+
fs.copyFileSync(settingsFile, backup);
|
|
49
|
+
console.log(`${green}✓ Backed up existing settings${reset}`);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
settings = {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
settings.statusLine = {
|
|
59
|
+
type: 'command',
|
|
60
|
+
command: `node ${scriptDest.replace(/\\/g, '/')}`
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
64
|
+
console.log(`${green}✓ Updated settings.json${reset}`);
|
|
65
|
+
|
|
66
|
+
console.log(`\n${green}======================================${reset}`);
|
|
67
|
+
console.log(`${green} Installation Complete!${reset}`);
|
|
68
|
+
console.log(`${green}======================================${reset}`);
|
|
69
|
+
console.log('\nRestart Claude Code or start a new session.');
|
|
70
|
+
console.log('The statusline will auto-detect your setup (subscription vs API key).\n');
|
package/install.ps1
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Claude Code Enhanced Statusline - Windows PowerShell Installation Script
|
|
2
|
+
|
|
3
|
+
$ErrorActionPreference = "Stop"
|
|
4
|
+
|
|
5
|
+
$HOOKS_DIR = "$env:USERPROFILE\.claude\hooks"
|
|
6
|
+
$SETTINGS_FILE = "$env:USERPROFILE\.claude\settings.json"
|
|
7
|
+
$REPO_URL = "https://raw.githubusercontent.com/TahaSabir0/claude-statusline/main"
|
|
8
|
+
|
|
9
|
+
Write-Host "======================================" -ForegroundColor Cyan
|
|
10
|
+
Write-Host " Claude Code Statusline Installer" -ForegroundColor Cyan
|
|
11
|
+
Write-Host "======================================" -ForegroundColor Cyan
|
|
12
|
+
Write-Host ""
|
|
13
|
+
|
|
14
|
+
# Check if Claude Code is installed
|
|
15
|
+
if (-not (Test-Path "$env:USERPROFILE\.claude")) {
|
|
16
|
+
Write-Host "Error: Claude Code not found!" -ForegroundColor Red
|
|
17
|
+
Write-Host "Please install Claude Code first: https://github.com/anthropics/claude-code"
|
|
18
|
+
exit 1
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Create hooks directory if it doesn't exist
|
|
22
|
+
New-Item -ItemType Directory -Force -Path $HOOKS_DIR | Out-Null
|
|
23
|
+
|
|
24
|
+
# Prompt user for version choice
|
|
25
|
+
Write-Host "Which version would you like to install?"
|
|
26
|
+
Write-Host " 1) Full version (with API usage tracking)"
|
|
27
|
+
Write-Host " 2) Lite version (faster, no API calls)"
|
|
28
|
+
Write-Host ""
|
|
29
|
+
$choice = Read-Host "Enter your choice (1 or 2)"
|
|
30
|
+
|
|
31
|
+
switch ($choice) {
|
|
32
|
+
"1" {
|
|
33
|
+
$SCRIPT_NAME = "statusline.js"
|
|
34
|
+
$VERSION_NAME = "Full version"
|
|
35
|
+
}
|
|
36
|
+
"2" {
|
|
37
|
+
$SCRIPT_NAME = "statusline-lite.js"
|
|
38
|
+
$VERSION_NAME = "Lite version"
|
|
39
|
+
}
|
|
40
|
+
default {
|
|
41
|
+
Write-Host "Invalid choice. Exiting." -ForegroundColor Red
|
|
42
|
+
exit 1
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Download the script
|
|
47
|
+
Write-Host ""
|
|
48
|
+
Write-Host "Downloading $VERSION_NAME..." -ForegroundColor Yellow
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
$url = "$REPO_URL/$SCRIPT_NAME"
|
|
52
|
+
$output = "$HOOKS_DIR\$SCRIPT_NAME"
|
|
53
|
+
Invoke-WebRequest -Uri $url -OutFile $output -UseBasicParsing
|
|
54
|
+
Write-Host "✓ Downloaded and installed $SCRIPT_NAME" -ForegroundColor Green
|
|
55
|
+
} catch {
|
|
56
|
+
Write-Host "Error downloading script: $_" -ForegroundColor Red
|
|
57
|
+
exit 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Update settings.json
|
|
61
|
+
Write-Host ""
|
|
62
|
+
Write-Host "Updating Claude Code settings..." -ForegroundColor Yellow
|
|
63
|
+
|
|
64
|
+
# Backup existing settings
|
|
65
|
+
if (Test-Path $SETTINGS_FILE) {
|
|
66
|
+
$timestamp = Get-Date -Format "yyyyMMddHHmmss"
|
|
67
|
+
Copy-Item $SETTINGS_FILE "$SETTINGS_FILE.backup.$timestamp"
|
|
68
|
+
Write-Host "✓ Backed up existing settings" -ForegroundColor Green
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Read or create settings
|
|
72
|
+
$settings = @{}
|
|
73
|
+
if (Test-Path $SETTINGS_FILE) {
|
|
74
|
+
try {
|
|
75
|
+
$settings = Get-Content $SETTINGS_FILE | ConvertFrom-Json -AsHashtable
|
|
76
|
+
} catch {
|
|
77
|
+
# If parsing fails, start with empty settings
|
|
78
|
+
$settings = @{}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Update statusLine setting (use forward slashes for cross-platform compatibility)
|
|
83
|
+
$commandPath = "$HOOKS_DIR\$SCRIPT_NAME" -replace '\\', '/'
|
|
84
|
+
$settings.statusLine = @{
|
|
85
|
+
type = "command"
|
|
86
|
+
command = "node $commandPath"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Write back to file
|
|
90
|
+
$settings | ConvertTo-Json -Depth 10 | Set-Content $SETTINGS_FILE
|
|
91
|
+
Write-Host "✓ Updated settings.json" -ForegroundColor Green
|
|
92
|
+
|
|
93
|
+
# Success message
|
|
94
|
+
Write-Host ""
|
|
95
|
+
Write-Host "======================================" -ForegroundColor Green
|
|
96
|
+
Write-Host " Installation Complete! ✓" -ForegroundColor Green
|
|
97
|
+
Write-Host "======================================" -ForegroundColor Green
|
|
98
|
+
Write-Host ""
|
|
99
|
+
Write-Host "Next steps:"
|
|
100
|
+
Write-Host " 1. Restart Claude Code or start a new session"
|
|
101
|
+
Write-Host " 2. Your statusline should now be active!"
|
|
102
|
+
Write-Host ""
|
|
103
|
+
Write-Host "To switch versions:"
|
|
104
|
+
Write-Host " - Run this installer again, or"
|
|
105
|
+
Write-Host " - Edit ~/.claude/settings.json manually"
|
|
106
|
+
Write-Host ""
|
|
107
|
+
Write-Host "To uninstall:"
|
|
108
|
+
Write-Host " - Remove ~/.claude/hooks/$SCRIPT_NAME"
|
|
109
|
+
Write-Host " - Remove the 'statusLine' section from ~/.claude/settings.json"
|
|
110
|
+
Write-Host ""
|
|
111
|
+
Write-Host "For help, visit: https://github.com/TahaSabir0/claude-statusline"
|
|
112
|
+
Write-Host ""
|
package/install.sh
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claude Code Enhanced Statusline - Installation Script
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
HOOKS_DIR="$HOME/.claude/hooks"
|
|
7
|
+
SETTINGS_FILE="$HOME/.claude/settings.json"
|
|
8
|
+
REPO_URL="https://raw.githubusercontent.com/TahaSabir0/claude-statusline/main"
|
|
9
|
+
|
|
10
|
+
# Colors
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
RED='\033[0;31m'
|
|
14
|
+
NC='\033[0m' # No Color
|
|
15
|
+
|
|
16
|
+
echo "======================================"
|
|
17
|
+
echo " Claude Code Statusline Installer"
|
|
18
|
+
echo "======================================"
|
|
19
|
+
echo ""
|
|
20
|
+
|
|
21
|
+
# Check if Claude Code is installed
|
|
22
|
+
if [ ! -d "$HOME/.claude" ]; then
|
|
23
|
+
echo -e "${RED}Error: Claude Code not found!${NC}"
|
|
24
|
+
echo "Please install Claude Code first: https://github.com/anthropics/claude-code"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Create hooks directory if it doesn't exist
|
|
29
|
+
mkdir -p "$HOOKS_DIR"
|
|
30
|
+
|
|
31
|
+
# Prompt user for version choice
|
|
32
|
+
echo "Which version would you like to install?"
|
|
33
|
+
echo " 1) Full version (with API usage tracking)"
|
|
34
|
+
echo " 2) Lite version (faster, no API calls)"
|
|
35
|
+
echo ""
|
|
36
|
+
read -p "Enter your choice (1 or 2): " choice
|
|
37
|
+
|
|
38
|
+
case $choice in
|
|
39
|
+
1)
|
|
40
|
+
SCRIPT_NAME="statusline.js"
|
|
41
|
+
VERSION_NAME="Full version"
|
|
42
|
+
;;
|
|
43
|
+
2)
|
|
44
|
+
SCRIPT_NAME="statusline-lite.js"
|
|
45
|
+
VERSION_NAME="Lite version"
|
|
46
|
+
;;
|
|
47
|
+
*)
|
|
48
|
+
echo -e "${RED}Invalid choice. Exiting.${NC}"
|
|
49
|
+
exit 1
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
# Download the script
|
|
54
|
+
echo ""
|
|
55
|
+
echo -e "${YELLOW}Downloading $VERSION_NAME...${NC}"
|
|
56
|
+
|
|
57
|
+
if command -v curl &> /dev/null; then
|
|
58
|
+
curl -fsSL "$REPO_URL/$SCRIPT_NAME" -o "$HOOKS_DIR/$SCRIPT_NAME"
|
|
59
|
+
elif command -v wget &> /dev/null; then
|
|
60
|
+
wget -q "$REPO_URL/$SCRIPT_NAME" -O "$HOOKS_DIR/$SCRIPT_NAME"
|
|
61
|
+
else
|
|
62
|
+
echo -e "${RED}Error: Neither curl nor wget found. Please install one of them.${NC}"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Make executable
|
|
67
|
+
chmod +x "$HOOKS_DIR/$SCRIPT_NAME"
|
|
68
|
+
|
|
69
|
+
echo -e "${GREEN}✓ Downloaded and installed $SCRIPT_NAME${NC}"
|
|
70
|
+
|
|
71
|
+
# Update settings.json
|
|
72
|
+
echo ""
|
|
73
|
+
echo -e "${YELLOW}Updating Claude Code settings...${NC}"
|
|
74
|
+
|
|
75
|
+
# Backup existing settings
|
|
76
|
+
if [ -f "$SETTINGS_FILE" ]; then
|
|
77
|
+
cp "$SETTINGS_FILE" "$SETTINGS_FILE.backup.$(date +%s)"
|
|
78
|
+
echo -e "${GREEN}✓ Backed up existing settings${NC}"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Check if settings.json exists
|
|
82
|
+
if [ ! -f "$SETTINGS_FILE" ]; then
|
|
83
|
+
# Create new settings file
|
|
84
|
+
cat > "$SETTINGS_FILE" << EOF
|
|
85
|
+
{
|
|
86
|
+
"statusLine": {
|
|
87
|
+
"type": "command",
|
|
88
|
+
"command": "node $HOOKS_DIR/$SCRIPT_NAME"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
EOF
|
|
92
|
+
echo -e "${GREEN}✓ Created new settings.json${NC}"
|
|
93
|
+
else
|
|
94
|
+
# Update existing settings.json using node
|
|
95
|
+
node << EOF
|
|
96
|
+
const fs = require('fs');
|
|
97
|
+
const path = require('path');
|
|
98
|
+
|
|
99
|
+
const settingsPath = path.join(process.env.HOME, '.claude', 'settings.json');
|
|
100
|
+
let settings = {};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// File doesn't exist or is invalid JSON
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
settings.statusLine = {
|
|
109
|
+
type: 'command',
|
|
110
|
+
command: 'node $HOOKS_DIR/$SCRIPT_NAME'
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
114
|
+
console.log('Settings updated successfully');
|
|
115
|
+
EOF
|
|
116
|
+
|
|
117
|
+
if [ $? -eq 0 ]; then
|
|
118
|
+
echo -e "${GREEN}✓ Updated settings.json${NC}"
|
|
119
|
+
else
|
|
120
|
+
echo -e "${RED}✗ Failed to update settings.json automatically${NC}"
|
|
121
|
+
echo ""
|
|
122
|
+
echo "Please manually add this to $SETTINGS_FILE:"
|
|
123
|
+
echo ""
|
|
124
|
+
echo ' "statusLine": {'
|
|
125
|
+
echo ' "type": "command",'
|
|
126
|
+
echo " \"command\": \"node $HOOKS_DIR/$SCRIPT_NAME\""
|
|
127
|
+
echo ' }'
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Success message
|
|
132
|
+
echo ""
|
|
133
|
+
echo -e "${GREEN}======================================"
|
|
134
|
+
echo " Installation Complete! ✓"
|
|
135
|
+
echo "======================================${NC}"
|
|
136
|
+
echo ""
|
|
137
|
+
echo "Next steps:"
|
|
138
|
+
echo " 1. Restart Claude Code or start a new session"
|
|
139
|
+
echo " 2. Your statusline should now be active!"
|
|
140
|
+
echo ""
|
|
141
|
+
echo "To switch versions:"
|
|
142
|
+
echo " - Run this installer again, or"
|
|
143
|
+
echo " - Edit ~/.claude/settings.json manually"
|
|
144
|
+
echo ""
|
|
145
|
+
echo "To uninstall:"
|
|
146
|
+
echo " - Remove ~/.claude/hooks/$SCRIPT_NAME"
|
|
147
|
+
echo " - Remove the 'statusLine' section from ~/.claude/settings.json"
|
|
148
|
+
echo ""
|
|
149
|
+
echo "For help, visit: https://github.com/TahaSabir0/claude-statusline"
|
|
150
|
+
echo ""
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-best-statusline",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A customizable statusline for Claude Code that tracks context usage and session limits",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claude-best-statusline": "bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"statusline",
|
|
12
|
+
"vibe-coding",
|
|
13
|
+
"context-window"
|
|
14
|
+
],
|
|
15
|
+
"author": "TahaSabir0",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/TahaSabir0/claude-statusline"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/preview.png
ADDED
|
Binary file
|
package/statusline.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code Enhanced Statusline
|
|
3
|
+
// Shows: directory | model | context usage | API usage (5-hour limit) | current task
|
|
4
|
+
// Auto-detects API key vs subscription usage
|
|
5
|
+
// https://github.com/TahaSabir0/claude-statusline
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
|
|
12
|
+
const IS_API_KEY = !!process.env.ANTHROPIC_API_KEY;
|
|
13
|
+
|
|
14
|
+
// Cache configuration
|
|
15
|
+
const CACHE_DIR = path.join(os.homedir(), '.claude', 'cache');
|
|
16
|
+
const USAGE_CACHE_FILE = path.join(CACHE_DIR, 'usage-cache.json');
|
|
17
|
+
const CACHE_TTL_MS = 30000; // Cache valid for 30 seconds
|
|
18
|
+
|
|
19
|
+
// ANSI color codes
|
|
20
|
+
const colors = {
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
23
|
+
bold: '\x1b[1m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
orange: '\x1b[38;5;208m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
blink: '\x1b[5m'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function getUsageColor(percentage) {
|
|
32
|
+
if (percentage < 50) return colors.green;
|
|
33
|
+
if (percentage < 75) return colors.yellow;
|
|
34
|
+
if (percentage < 90) return colors.orange;
|
|
35
|
+
return colors.red;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getContextBar(remaining) {
|
|
39
|
+
const effectiveRemaining = remaining ?? 89;
|
|
40
|
+
const used = Math.max(0, Math.min(100, 100 - Math.round(effectiveRemaining)));
|
|
41
|
+
|
|
42
|
+
const filled = Math.floor(used / 10);
|
|
43
|
+
const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(10 - filled);
|
|
44
|
+
|
|
45
|
+
let coloredBar;
|
|
46
|
+
if (used < 50) {
|
|
47
|
+
coloredBar = `${colors.green}${bar} ${used}%${colors.reset}`;
|
|
48
|
+
} else if (used < 65) {
|
|
49
|
+
coloredBar = `${colors.yellow}${bar} ${used}%${colors.reset}`;
|
|
50
|
+
} else if (used < 80) {
|
|
51
|
+
coloredBar = `${colors.orange}${bar} ${used}%${colors.reset}`;
|
|
52
|
+
} else {
|
|
53
|
+
coloredBar = `${colors.blink}${colors.red}\u{1F480} ${bar} ${used}%${colors.reset}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return coloredBar;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Read cached usage data
|
|
60
|
+
function getCachedUsage() {
|
|
61
|
+
try {
|
|
62
|
+
if (!fs.existsSync(USAGE_CACHE_FILE)) return null;
|
|
63
|
+
|
|
64
|
+
const cache = JSON.parse(fs.readFileSync(USAGE_CACHE_FILE, 'utf8'));
|
|
65
|
+
const age = Date.now() - cache.timestamp;
|
|
66
|
+
|
|
67
|
+
// Return cached data if fresh enough
|
|
68
|
+
if (age < CACHE_TTL_MS) {
|
|
69
|
+
return cache.data;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Write usage data to cache (shared across all sessions)
|
|
79
|
+
function setCachedUsage(data) {
|
|
80
|
+
try {
|
|
81
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
82
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const cache = {
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
data: data
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
fs.writeFileSync(USAGE_CACHE_FILE, JSON.stringify(cache), 'utf8');
|
|
91
|
+
} catch (e) {
|
|
92
|
+
// Silently fail
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getApiUsage(callback) {
|
|
97
|
+
try {
|
|
98
|
+
// Read credentials
|
|
99
|
+
const credsPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
100
|
+
if (!fs.existsSync(credsPath)) {
|
|
101
|
+
return callback(null);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const creds = JSON.parse(fs.readFileSync(credsPath, 'utf8'));
|
|
105
|
+
const accessToken = creds.claudeAiOauth?.accessToken;
|
|
106
|
+
|
|
107
|
+
if (!accessToken) {
|
|
108
|
+
return callback(null);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Adaptive timeout: if cache exists, be faster (1200ms); if not, be patient (1500ms)
|
|
112
|
+
// API typically takes ~850ms, so 1200ms gives reasonable headroom
|
|
113
|
+
const hasCache = fs.existsSync(USAGE_CACHE_FILE);
|
|
114
|
+
const timeout = hasCache ? 1200 : 1500;
|
|
115
|
+
|
|
116
|
+
// Make API call with adaptive timeout
|
|
117
|
+
const req = https.request({
|
|
118
|
+
hostname: 'api.anthropic.com',
|
|
119
|
+
path: '/api/oauth/usage',
|
|
120
|
+
method: 'GET',
|
|
121
|
+
headers: {
|
|
122
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
'anthropic-beta': 'oauth-2025-04-20'
|
|
125
|
+
},
|
|
126
|
+
timeout: timeout
|
|
127
|
+
}, (res) => {
|
|
128
|
+
let data = '';
|
|
129
|
+
|
|
130
|
+
res.on('data', chunk => data += chunk);
|
|
131
|
+
res.on('end', () => {
|
|
132
|
+
try {
|
|
133
|
+
const usage = JSON.parse(data);
|
|
134
|
+
|
|
135
|
+
// Get 5-hour session usage
|
|
136
|
+
if (usage.five_hour) {
|
|
137
|
+
const percentage = Math.round(usage.five_hour.utilization);
|
|
138
|
+
const resetsAt = usage.five_hour.resets_at;
|
|
139
|
+
|
|
140
|
+
// Parse reset time
|
|
141
|
+
let timeStr = '';
|
|
142
|
+
if (resetsAt) {
|
|
143
|
+
const resetDate = new Date(resetsAt);
|
|
144
|
+
const now = new Date();
|
|
145
|
+
const diffMs = resetDate - now;
|
|
146
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
147
|
+
const hours = Math.floor(diffMins / 60);
|
|
148
|
+
const mins = diffMins % 60;
|
|
149
|
+
|
|
150
|
+
if (hours > 0) {
|
|
151
|
+
timeStr = `${hours}h${mins}m`;
|
|
152
|
+
} else {
|
|
153
|
+
timeStr = `${mins}m`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Build bar
|
|
158
|
+
const barWidth = 10;
|
|
159
|
+
const filledWidth = Math.round((percentage / 100) * barWidth);
|
|
160
|
+
const filled = '\u2588'.repeat(filledWidth);
|
|
161
|
+
const empty = '\u2591'.repeat(barWidth - filledWidth);
|
|
162
|
+
const color = getUsageColor(percentage);
|
|
163
|
+
|
|
164
|
+
const bar = `${color}${filled}${empty} ${percentage}%${colors.reset}${colors.dim} (${timeStr})${colors.reset}`;
|
|
165
|
+
|
|
166
|
+
// Cache the result for other sessions
|
|
167
|
+
setCachedUsage(bar);
|
|
168
|
+
|
|
169
|
+
callback(bar);
|
|
170
|
+
} else {
|
|
171
|
+
callback(null);
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
callback(null);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
req.on('error', () => callback(null));
|
|
180
|
+
req.on('timeout', () => {
|
|
181
|
+
req.destroy();
|
|
182
|
+
callback(null);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
req.end();
|
|
186
|
+
} catch (e) {
|
|
187
|
+
callback(null);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Get usage with cache fallback
|
|
192
|
+
function getUsageWithCache(callback) {
|
|
193
|
+
// First, try to get fresh data from API
|
|
194
|
+
getApiUsage((freshData) => {
|
|
195
|
+
if (freshData) {
|
|
196
|
+
// Got fresh data, use it
|
|
197
|
+
callback(freshData);
|
|
198
|
+
} else {
|
|
199
|
+
// API failed or timed out, try cache
|
|
200
|
+
const cachedData = getCachedUsage();
|
|
201
|
+
callback(cachedData);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getCurrentTask(sessionId) {
|
|
207
|
+
if (!sessionId) return '';
|
|
208
|
+
|
|
209
|
+
const homeDir = os.homedir();
|
|
210
|
+
const todosDir = path.join(homeDir, '.claude', 'todos');
|
|
211
|
+
|
|
212
|
+
if (!fs.existsSync(todosDir)) return '';
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const files = fs.readdirSync(todosDir)
|
|
216
|
+
.filter(f => f.startsWith(sessionId) && f.includes('-agent-') && f.endsWith('.json'))
|
|
217
|
+
.map(f => ({ name: f, mtime: fs.statSync(path.join(todosDir, f)).mtime }))
|
|
218
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
219
|
+
|
|
220
|
+
if (files.length > 0) {
|
|
221
|
+
const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));
|
|
222
|
+
const inProgress = todos.find(t => t.status === 'in_progress');
|
|
223
|
+
if (inProgress) return inProgress.activeForm || '';
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {}
|
|
226
|
+
|
|
227
|
+
return '';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Main
|
|
231
|
+
function outputStatus(data, usageBar) {
|
|
232
|
+
try {
|
|
233
|
+
const model = data?.model?.display_name || 'Claude';
|
|
234
|
+
const dir = data?.workspace?.current_dir || process.cwd();
|
|
235
|
+
const dirname = path.basename(dir);
|
|
236
|
+
const sessionId = data?.session_id || '';
|
|
237
|
+
const remaining = data?.context_window?.remaining_percentage;
|
|
238
|
+
|
|
239
|
+
const contextBar = getContextBar(remaining);
|
|
240
|
+
const task = getCurrentTask(sessionId);
|
|
241
|
+
const parts = [];
|
|
242
|
+
parts.push(dirname);
|
|
243
|
+
parts.push(model);
|
|
244
|
+
parts.push(`context: ${contextBar}`);
|
|
245
|
+
|
|
246
|
+
if (usageBar) {
|
|
247
|
+
parts.push(`usage: ${usageBar}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (task) parts.push(`${colors.dim}${task}${colors.reset}`);
|
|
251
|
+
process.stdout.write(parts.join(' \u2502 '));
|
|
252
|
+
} catch (e) {
|
|
253
|
+
process.stdout.write('Status unavailable');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function outputFallback(usageBar) {
|
|
258
|
+
const contextBar = getContextBar(undefined);
|
|
259
|
+
const parts = ['~', 'Claude', `context: ${contextBar}`];
|
|
260
|
+
if (usageBar) parts.push(`usage: ${usageBar}`);
|
|
261
|
+
process.stdout.write(parts.join(' \u2502 '));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Wrapper that skips usage fetch for API key users
|
|
265
|
+
function getUsage(callback) {
|
|
266
|
+
if (IS_API_KEY) {
|
|
267
|
+
callback(null);
|
|
268
|
+
} else {
|
|
269
|
+
getUsageWithCache(callback);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Process with timeout
|
|
274
|
+
if (process.stdin.isTTY) {
|
|
275
|
+
getUsage((usageBar) => {
|
|
276
|
+
outputFallback(usageBar);
|
|
277
|
+
process.exit(0);
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
let input = '';
|
|
281
|
+
let timeoutReached = false;
|
|
282
|
+
|
|
283
|
+
const overallTimeout = IS_API_KEY ? 500 : (fs.existsSync(USAGE_CACHE_FILE) ? 1300 : 1600);
|
|
284
|
+
|
|
285
|
+
const timeout = setTimeout(() => {
|
|
286
|
+
timeoutReached = true;
|
|
287
|
+
getUsage((usageBar) => {
|
|
288
|
+
if (input.length > 0) {
|
|
289
|
+
try {
|
|
290
|
+
const data = JSON.parse(input);
|
|
291
|
+
outputStatus(data, usageBar);
|
|
292
|
+
} catch (e) {
|
|
293
|
+
outputFallback(usageBar);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
outputFallback(usageBar);
|
|
297
|
+
}
|
|
298
|
+
process.exit(0);
|
|
299
|
+
});
|
|
300
|
+
}, overallTimeout);
|
|
301
|
+
|
|
302
|
+
process.stdin.setEncoding('utf8');
|
|
303
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
304
|
+
process.stdin.on('end', () => {
|
|
305
|
+
if (timeoutReached) return;
|
|
306
|
+
clearTimeout(timeout);
|
|
307
|
+
|
|
308
|
+
getUsage((usageBar) => {
|
|
309
|
+
try {
|
|
310
|
+
const data = JSON.parse(input);
|
|
311
|
+
outputStatus(data, usageBar);
|
|
312
|
+
} catch (e) {
|
|
313
|
+
outputFallback(usageBar);
|
|
314
|
+
}
|
|
315
|
+
process.exit(0);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|