claude-statusline-setup 1.0.1
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/README.md +36 -0
- package/bin/install.js +127 -0
- package/files/statusline-command.sh +334 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# claude-statusline-setup
|
|
2
|
+
|
|
3
|
+
Setup Claude Code statusline with usage metrics display.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g claude-statusline-setup
|
|
9
|
+
claude-statusline-setup
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What it does
|
|
13
|
+
|
|
14
|
+
Installs a custom statusline for Claude Code that shows:
|
|
15
|
+
|
|
16
|
+
- Current model name (Opus, Sonnet, Haiku)
|
|
17
|
+
- Git branch
|
|
18
|
+
- Context window usage
|
|
19
|
+
- Session usage (5-hour rolling limit)
|
|
20
|
+
- Weekly usage (7-day rolling limit)
|
|
21
|
+
|
|
22
|
+
Example output:
|
|
23
|
+
```
|
|
24
|
+
Opus · main · Context 32% (65k/200k) · Session 70% @2pm · Week 6% @Jan 24, 9am
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- macOS (uses Keychain for token storage)
|
|
30
|
+
- Claude Code CLI
|
|
31
|
+
- Python 3.x
|
|
32
|
+
|
|
33
|
+
## Files installed
|
|
34
|
+
|
|
35
|
+
- `~/.claude/statusline-command.sh` - The statusline script
|
|
36
|
+
- `~/.claude/settings.json` - Updated with statusLine config
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
// ANSI colors
|
|
9
|
+
const GREEN = '\x1b[32m';
|
|
10
|
+
const YELLOW = '\x1b[33m';
|
|
11
|
+
const CYAN = '\x1b[36m';
|
|
12
|
+
const RED = '\x1b[31m';
|
|
13
|
+
const RESET = '\x1b[0m';
|
|
14
|
+
const BOLD = '\x1b[1m';
|
|
15
|
+
|
|
16
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
17
|
+
const STATUSLINE_FILE = 'statusline-command.sh';
|
|
18
|
+
const SETTINGS_FILE = 'settings.json';
|
|
19
|
+
|
|
20
|
+
function log(msg, color = '') {
|
|
21
|
+
console.log(`${color}${msg}${RESET}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureClaudeDir() {
|
|
25
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
26
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
27
|
+
log(`Created ${CLAUDE_DIR}`, GREEN);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function copyStatuslineScript() {
|
|
32
|
+
const src = path.join(__dirname, '..', 'files', STATUSLINE_FILE);
|
|
33
|
+
const dest = path.join(CLAUDE_DIR, STATUSLINE_FILE);
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(src)) {
|
|
36
|
+
log(`Error: Source file not found: ${src}`, RED);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if file already exists
|
|
41
|
+
if (fs.existsSync(dest)) {
|
|
42
|
+
const srcContent = fs.readFileSync(src, 'utf8');
|
|
43
|
+
const destContent = fs.readFileSync(dest, 'utf8');
|
|
44
|
+
if (srcContent === destContent) {
|
|
45
|
+
log(`${STATUSLINE_FILE} is already up to date`, CYAN);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
// Backup existing file
|
|
49
|
+
const backupPath = `${dest}.backup.${Date.now()}`;
|
|
50
|
+
fs.copyFileSync(dest, backupPath);
|
|
51
|
+
log(`Backed up existing file to ${backupPath}`, YELLOW);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fs.copyFileSync(src, dest);
|
|
55
|
+
fs.chmodSync(dest, 0o755);
|
|
56
|
+
log(`Installed ${STATUSLINE_FILE} to ${CLAUDE_DIR}`, GREEN);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function updateSettings() {
|
|
61
|
+
const settingsPath = path.join(CLAUDE_DIR, SETTINGS_FILE);
|
|
62
|
+
let settings = {};
|
|
63
|
+
|
|
64
|
+
// Read existing settings if present
|
|
65
|
+
if (fs.existsSync(settingsPath)) {
|
|
66
|
+
try {
|
|
67
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
log(`Warning: Could not parse existing ${SETTINGS_FILE}`, YELLOW);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if statusLine is already configured
|
|
74
|
+
const statusLineConfig = {
|
|
75
|
+
type: 'command',
|
|
76
|
+
command: `~/.claude/${STATUSLINE_FILE}`,
|
|
77
|
+
padding: 0
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (settings.statusLine &&
|
|
81
|
+
settings.statusLine.type === 'command' &&
|
|
82
|
+
settings.statusLine.command === statusLineConfig.command) {
|
|
83
|
+
log('statusLine is already configured', CYAN);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update statusLine config
|
|
88
|
+
settings.statusLine = statusLineConfig;
|
|
89
|
+
|
|
90
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
91
|
+
log(`Updated ${SETTINGS_FILE} with statusLine config`, GREEN);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function showSuccess() {
|
|
95
|
+
console.log('');
|
|
96
|
+
log('='.repeat(50), CYAN);
|
|
97
|
+
log(`${BOLD}Claude Statusline Setup Complete!${RESET}`, GREEN);
|
|
98
|
+
log('='.repeat(50), CYAN);
|
|
99
|
+
console.log('');
|
|
100
|
+
log('Your statusline will show:', CYAN);
|
|
101
|
+
log(' - Current model name');
|
|
102
|
+
log(' - Git branch');
|
|
103
|
+
log(' - Context window usage');
|
|
104
|
+
log(' - Session usage (5-hour limit)');
|
|
105
|
+
log(' - Weekly usage (7-day limit)');
|
|
106
|
+
console.log('');
|
|
107
|
+
log('Restart Claude Code to see the changes.', YELLOW);
|
|
108
|
+
console.log('');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function main() {
|
|
112
|
+
log(`${BOLD}Claude Statusline Setup${RESET}`, CYAN);
|
|
113
|
+
log('-'.repeat(30), CYAN);
|
|
114
|
+
console.log('');
|
|
115
|
+
|
|
116
|
+
ensureClaudeDir();
|
|
117
|
+
|
|
118
|
+
if (copyStatuslineScript()) {
|
|
119
|
+
updateSettings();
|
|
120
|
+
showSuccess();
|
|
121
|
+
} else {
|
|
122
|
+
log('Setup failed!', RED);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main();
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claude Code Statusline - 顯示 session 使用量資訊
|
|
4
|
+
|
|
5
|
+
輸出格式:
|
|
6
|
+
Opus · main · Context 32% (65k/200k) · Session 70% @2pm · Week 6% @Jan 24, 9am
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from urllib.request import Request, urlopen
|
|
17
|
+
from urllib.error import URLError, HTTPError
|
|
18
|
+
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# 設定
|
|
21
|
+
# ============================================================================
|
|
22
|
+
|
|
23
|
+
CACHE_FILE = Path("/tmp/claude-usage-cache.json")
|
|
24
|
+
CACHE_TTL = 300 # 快取有效期(秒)
|
|
25
|
+
API_URL = "https://api.anthropic.com/api/oauth/usage"
|
|
26
|
+
TIMEZONE_OFFSET = 8 # Asia/Taipei UTC+8
|
|
27
|
+
DEFAULT_CONTEXT_SIZE = 200_000
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# ANSI 色碼
|
|
31
|
+
# ============================================================================
|
|
32
|
+
|
|
33
|
+
GREEN = "\033[32m"
|
|
34
|
+
YELLOW = "\033[33m"
|
|
35
|
+
ORANGE = "\033[38;5;208m"
|
|
36
|
+
RED = "\033[31m"
|
|
37
|
+
CYAN = "\033[36m"
|
|
38
|
+
MAGENTA = "\033[35m"
|
|
39
|
+
BOLD = "\033[1m"
|
|
40
|
+
DIM = "\033[2m"
|
|
41
|
+
RESET = "\033[0m"
|
|
42
|
+
|
|
43
|
+
SEP = f" {DIM}·{RESET} "
|
|
44
|
+
|
|
45
|
+
# ============================================================================
|
|
46
|
+
# 顏色判斷
|
|
47
|
+
# ============================================================================
|
|
48
|
+
|
|
49
|
+
def get_color(percentage: float) -> str:
|
|
50
|
+
"""根據使用量百分比回傳對應顏色"""
|
|
51
|
+
if percentage >= 80:
|
|
52
|
+
return RED
|
|
53
|
+
elif percentage >= 50:
|
|
54
|
+
return YELLOW
|
|
55
|
+
return GREEN
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_context_color(percentage: float) -> str:
|
|
59
|
+
"""根據 context 使用量百分比回傳對應顏色(更細緻的分級)"""
|
|
60
|
+
if percentage >= 80:
|
|
61
|
+
return RED
|
|
62
|
+
elif percentage >= 70:
|
|
63
|
+
return ORANGE
|
|
64
|
+
elif percentage >= 50:
|
|
65
|
+
return YELLOW
|
|
66
|
+
return GREEN
|
|
67
|
+
|
|
68
|
+
# ============================================================================
|
|
69
|
+
# Token 相關
|
|
70
|
+
# ============================================================================
|
|
71
|
+
|
|
72
|
+
def get_token_from_keychain() -> str | None:
|
|
73
|
+
"""從 macOS Keychain 取得 OAuth token"""
|
|
74
|
+
try:
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
["security", "find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
timeout=5
|
|
80
|
+
)
|
|
81
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
82
|
+
creds = json.loads(result.stdout.strip())
|
|
83
|
+
return creds.get("claudeAiOauth", {}).get("accessToken")
|
|
84
|
+
except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError):
|
|
85
|
+
pass
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_token_from_file() -> str | None:
|
|
90
|
+
"""從檔案取得 OAuth token"""
|
|
91
|
+
creds_file = Path.home() / ".claude" / ".credentials.json"
|
|
92
|
+
try:
|
|
93
|
+
if creds_file.exists():
|
|
94
|
+
with open(creds_file) as f:
|
|
95
|
+
creds = json.load(f)
|
|
96
|
+
return creds.get("claudeAiOauth", {}).get("accessToken")
|
|
97
|
+
except (json.JSONDecodeError, IOError):
|
|
98
|
+
pass
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_token() -> str | None:
|
|
103
|
+
"""取得 OAuth token(優先 Keychain,fallback 檔案)"""
|
|
104
|
+
return get_token_from_keychain() or get_token_from_file()
|
|
105
|
+
|
|
106
|
+
# ============================================================================
|
|
107
|
+
# API 與快取
|
|
108
|
+
# ============================================================================
|
|
109
|
+
|
|
110
|
+
def fetch_usage(token: str) -> dict | None:
|
|
111
|
+
"""呼叫 Usage API"""
|
|
112
|
+
headers = {
|
|
113
|
+
"Accept": "application/json",
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
"User-Agent": "claude-code/2.0.32",
|
|
116
|
+
"Authorization": f"Bearer {token}",
|
|
117
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
118
|
+
}
|
|
119
|
+
try:
|
|
120
|
+
req = Request(API_URL, headers=headers, method="GET")
|
|
121
|
+
with urlopen(req, timeout=10) as response:
|
|
122
|
+
return json.loads(response.read().decode())
|
|
123
|
+
except (URLError, HTTPError, json.JSONDecodeError):
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def load_cache() -> dict | None:
|
|
128
|
+
"""載入快取"""
|
|
129
|
+
try:
|
|
130
|
+
if CACHE_FILE.exists():
|
|
131
|
+
with open(CACHE_FILE) as f:
|
|
132
|
+
cache = json.load(f)
|
|
133
|
+
if time.time() - cache.get("timestamp", 0) < CACHE_TTL:
|
|
134
|
+
return cache.get("data")
|
|
135
|
+
except (json.JSONDecodeError, IOError):
|
|
136
|
+
pass
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def save_cache(data: dict) -> None:
|
|
141
|
+
"""儲存快取"""
|
|
142
|
+
try:
|
|
143
|
+
with open(CACHE_FILE, "w") as f:
|
|
144
|
+
json.dump({"timestamp": time.time(), "data": data}, f)
|
|
145
|
+
except IOError:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
# ============================================================================
|
|
149
|
+
# 格式化工具
|
|
150
|
+
# ============================================================================
|
|
151
|
+
|
|
152
|
+
def format_hour(hour: int, minute: int) -> str:
|
|
153
|
+
"""將小時和分鐘格式化為 12 小時制"""
|
|
154
|
+
if hour == 0:
|
|
155
|
+
hour_str, ampm = "12", "am"
|
|
156
|
+
elif hour < 12:
|
|
157
|
+
hour_str, ampm = str(hour), "am"
|
|
158
|
+
elif hour == 12:
|
|
159
|
+
hour_str, ampm = "12", "pm"
|
|
160
|
+
else:
|
|
161
|
+
hour_str, ampm = str(hour - 12), "pm"
|
|
162
|
+
|
|
163
|
+
if minute > 0:
|
|
164
|
+
return f"{hour_str}:{minute:02d}{ampm}"
|
|
165
|
+
return f"{hour_str}{ampm}"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def format_time(iso_time: str | None) -> str:
|
|
169
|
+
"""將 UTC 時間轉換為本地時間格式(例:2pm, 1:59pm)"""
|
|
170
|
+
if not iso_time:
|
|
171
|
+
return "N/A"
|
|
172
|
+
try:
|
|
173
|
+
dt = datetime.fromisoformat(iso_time.replace("Z", "+00:00"))
|
|
174
|
+
local_dt = dt + timedelta(hours=TIMEZONE_OFFSET)
|
|
175
|
+
return format_hour(local_dt.hour, local_dt.minute)
|
|
176
|
+
except (ValueError, AttributeError):
|
|
177
|
+
return "N/A"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def format_week_reset(iso_time: str | None) -> str:
|
|
181
|
+
"""將 UTC 時間轉換為日期格式(例:Jan 24, 9am)"""
|
|
182
|
+
if not iso_time:
|
|
183
|
+
return ""
|
|
184
|
+
try:
|
|
185
|
+
dt = datetime.fromisoformat(iso_time.replace("Z", "+00:00"))
|
|
186
|
+
local_dt = dt + timedelta(hours=TIMEZONE_OFFSET)
|
|
187
|
+
|
|
188
|
+
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
189
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
190
|
+
month_str = months[local_dt.month - 1]
|
|
191
|
+
time_str = format_hour(local_dt.hour, local_dt.minute)
|
|
192
|
+
|
|
193
|
+
return f"{month_str} {local_dt.day}, {time_str}"
|
|
194
|
+
except (ValueError, AttributeError):
|
|
195
|
+
return ""
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def format_tokens(tokens: int) -> str:
|
|
199
|
+
"""格式化 token 數量(例:65k, 1.5M)"""
|
|
200
|
+
if tokens >= 1_000_000:
|
|
201
|
+
return f"{tokens / 1_000_000:.1f}M"
|
|
202
|
+
elif tokens >= 1_000:
|
|
203
|
+
return f"{tokens // 1_000}k"
|
|
204
|
+
return str(tokens)
|
|
205
|
+
|
|
206
|
+
# ============================================================================
|
|
207
|
+
# Context 計算
|
|
208
|
+
# ============================================================================
|
|
209
|
+
|
|
210
|
+
def calculate_context_usage(context_window: dict | None) -> tuple[int, int, float]:
|
|
211
|
+
"""
|
|
212
|
+
計算 context window 使用量
|
|
213
|
+
回傳: (used_tokens, total_tokens, percentage)
|
|
214
|
+
"""
|
|
215
|
+
if not context_window:
|
|
216
|
+
return 0, DEFAULT_CONTEXT_SIZE, 0.0
|
|
217
|
+
|
|
218
|
+
context_size = context_window.get("context_window_size", DEFAULT_CONTEXT_SIZE)
|
|
219
|
+
|
|
220
|
+
# 優先使用 current_usage(更準確)
|
|
221
|
+
current_usage = context_window.get("current_usage")
|
|
222
|
+
if current_usage:
|
|
223
|
+
used_tokens = (
|
|
224
|
+
current_usage.get("input_tokens", 0) +
|
|
225
|
+
current_usage.get("cache_read_input_tokens", 0) +
|
|
226
|
+
current_usage.get("cache_creation_input_tokens", 0)
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
used_tokens = context_window.get("total_input_tokens", 0)
|
|
230
|
+
|
|
231
|
+
percentage = (used_tokens * 100) / context_size if context_size > 0 else 0
|
|
232
|
+
return used_tokens, context_size, percentage
|
|
233
|
+
|
|
234
|
+
# ============================================================================
|
|
235
|
+
# Git
|
|
236
|
+
# ============================================================================
|
|
237
|
+
|
|
238
|
+
def get_git_branch(cwd: str | None) -> str | None:
|
|
239
|
+
"""取得 git 分支名稱"""
|
|
240
|
+
try:
|
|
241
|
+
result = subprocess.run(
|
|
242
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
243
|
+
capture_output=True,
|
|
244
|
+
text=True,
|
|
245
|
+
cwd=cwd or os.getcwd(),
|
|
246
|
+
timeout=2
|
|
247
|
+
)
|
|
248
|
+
if result.returncode == 0:
|
|
249
|
+
return result.stdout.strip()
|
|
250
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
251
|
+
pass
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
# ============================================================================
|
|
255
|
+
# 主程式
|
|
256
|
+
# ============================================================================
|
|
257
|
+
|
|
258
|
+
def main():
|
|
259
|
+
# 讀取 stdin(Claude Code 傳入的 JSON)
|
|
260
|
+
cwd = None
|
|
261
|
+
model_name = None
|
|
262
|
+
context_window = None
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
stdin_data = sys.stdin.read()
|
|
266
|
+
if stdin_data:
|
|
267
|
+
input_json = json.loads(stdin_data)
|
|
268
|
+
cwd = input_json.get("cwd") or input_json.get("workspace", {}).get("current_dir")
|
|
269
|
+
model_info = input_json.get("model", {})
|
|
270
|
+
model_name = model_info.get("display_name") or model_info.get("id")
|
|
271
|
+
context_window = input_json.get("context_window")
|
|
272
|
+
except (json.JSONDecodeError, IOError):
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
# 取得各項資訊
|
|
276
|
+
git_branch = get_git_branch(cwd)
|
|
277
|
+
ctx_used, ctx_total, ctx_percent = calculate_context_usage(context_window)
|
|
278
|
+
|
|
279
|
+
# 取得 API 使用量(從快取或 API)
|
|
280
|
+
data = load_cache()
|
|
281
|
+
if not data:
|
|
282
|
+
token = get_token()
|
|
283
|
+
if not token:
|
|
284
|
+
print(f"{DIM}No token{RESET}")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
data = fetch_usage(token)
|
|
288
|
+
if not data:
|
|
289
|
+
print(f"{DIM}API error{RESET}")
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
save_cache(data)
|
|
293
|
+
|
|
294
|
+
# 解析 API 資料
|
|
295
|
+
five_hour = data.get("five_hour", {})
|
|
296
|
+
seven_day = data.get("seven_day", {})
|
|
297
|
+
|
|
298
|
+
session_util = five_hour.get("utilization", 0)
|
|
299
|
+
session_reset = format_time(five_hour.get("resets_at"))
|
|
300
|
+
week_util = seven_day.get("utilization", 0)
|
|
301
|
+
week_reset = format_week_reset(seven_day.get("resets_at"))
|
|
302
|
+
|
|
303
|
+
# 取得顏色
|
|
304
|
+
session_color = get_color(session_util)
|
|
305
|
+
week_color = get_color(week_util)
|
|
306
|
+
ctx_color = get_context_color(ctx_percent)
|
|
307
|
+
|
|
308
|
+
# 組合輸出
|
|
309
|
+
parts = []
|
|
310
|
+
|
|
311
|
+
if model_name:
|
|
312
|
+
parts.append(f"{BOLD}{MAGENTA}{model_name}{RESET}")
|
|
313
|
+
|
|
314
|
+
if git_branch:
|
|
315
|
+
parts.append(f"{CYAN}{git_branch}{RESET}")
|
|
316
|
+
|
|
317
|
+
parts.append(
|
|
318
|
+
f"Context {ctx_color}{ctx_percent:.0f}%{RESET} "
|
|
319
|
+
f"{DIM}({format_tokens(ctx_used)}/{format_tokens(ctx_total)}){RESET}"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
parts.append(
|
|
323
|
+
f"Session {session_color}{session_util:.0f}%{RESET} "
|
|
324
|
+
f"{DIM}@{session_reset}{RESET}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
week_reset_str = f" {DIM}@{week_reset}{RESET}" if week_reset else ""
|
|
328
|
+
parts.append(f"Week {week_color}{week_util:.0f}%{RESET}{week_reset_str}")
|
|
329
|
+
|
|
330
|
+
print(SEP.join(parts))
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
if __name__ == "__main__":
|
|
334
|
+
main()
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-statusline-setup",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Setup Claude Code statusline with usage metrics display",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claude-statusline-setup": "./bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"files/"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"statusline",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"author": "Alan",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/lms0016/claude-statusline-setup.git"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=16.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|