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 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
+ ![preview](preview.png)
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
+ }