@say8425/cc-statusline 2.0.2 → 2.1.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 +10 -0
- package/dist/index.js +53 -115
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,14 @@ Add the following to `~/.claude/settings.json`:
|
|
|
37
37
|
|
|
38
38
|

|
|
39
39
|
|
|
40
|
+
### Worktree
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
### Worktree + Usage Metrics
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
40
48
|
### Usage Metrics
|
|
41
49
|
|
|
42
50
|

|
|
@@ -48,6 +56,7 @@ Add the following to `~/.claude/settings.json`:
|
|
|
48
56
|
- **Context**: Token usage with percentage (color-coded)
|
|
49
57
|
- **Git Diff**: File count, insertions, deletions
|
|
50
58
|
- **PR URL**: Clickable OSC 8 hyperlink
|
|
59
|
+
- **Worktree Support**: Shows real project name when running in a `cc --worktree` session
|
|
51
60
|
- **TrueColor**: Dynamic colors based on thresholds
|
|
52
61
|
- **Limit Reset Time**: Reset time display (HH:MM)
|
|
53
62
|
- **Block Usage**: 5-hour utilization percentage (from server API)
|
|
@@ -59,6 +68,7 @@ Add the following to `~/.claude/settings.json`:
|
|
|
59
68
|
| Emoji | Description |
|
|
60
69
|
| ----- | ------------------------ |
|
|
61
70
|
| 📁 | Project folder name |
|
|
71
|
+
| 🌲 | Worktree name (shown in worktree sessions) |
|
|
62
72
|
| 🌿 | Current Git branch |
|
|
63
73
|
| ⏱️ | Session elapsed time |
|
|
64
74
|
| 💰 | Session cost in USD |
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
|
-
// src/cli.ts
|
|
5
|
-
function parseCliArgs(args) {
|
|
6
|
-
const showUsage = args.includes("--show-usage");
|
|
7
|
-
return { showUsage };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
4
|
// src/git/branch.ts
|
|
11
5
|
var {$ } = globalThis.Bun;
|
|
12
6
|
|
|
@@ -15,18 +9,13 @@ var cache = {
|
|
|
15
9
|
branch: { value: "", timestamp: 0 },
|
|
16
10
|
gitChanges: { files: 0, insertions: 0, deletions: 0, timestamp: 0 },
|
|
17
11
|
prUrl: { value: null, timestamp: 0 },
|
|
18
|
-
|
|
19
|
-
value: null,
|
|
20
|
-
timestamp: 0
|
|
21
|
-
},
|
|
22
|
-
accessToken: { value: null, timestamp: 0 }
|
|
12
|
+
mainProjectName: { value: null, timestamp: 0 }
|
|
23
13
|
};
|
|
24
14
|
var CACHE_TTL = {
|
|
25
15
|
branch: 5000,
|
|
26
16
|
gitChanges: 0,
|
|
27
17
|
prUrl: 30000,
|
|
28
|
-
|
|
29
|
-
accessToken: 300000
|
|
18
|
+
mainProjectName: 300000
|
|
30
19
|
};
|
|
31
20
|
|
|
32
21
|
// src/git/branch.ts
|
|
@@ -80,6 +69,27 @@ async function getPrUrlCached() {
|
|
|
80
69
|
return cache.prUrl.value;
|
|
81
70
|
}
|
|
82
71
|
}
|
|
72
|
+
// src/git/worktree.ts
|
|
73
|
+
var {$: $4 } = globalThis.Bun;
|
|
74
|
+
async function getMainProjectNameCached() {
|
|
75
|
+
if (Date.now() - cache.mainProjectName.timestamp < CACHE_TTL.mainProjectName) {
|
|
76
|
+
return cache.mainProjectName.value;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const result = await $4`git rev-parse --git-common-dir 2>/dev/null`.text();
|
|
80
|
+
const gitCommonDir = result.trim();
|
|
81
|
+
if (gitCommonDir === ".git") {
|
|
82
|
+
cache.mainProjectName = { value: null, timestamp: Date.now() };
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const projectRoot = gitCommonDir.replace(/\/.git$/, "");
|
|
86
|
+
const projectName = projectRoot.split("/").pop() || null;
|
|
87
|
+
cache.mainProjectName = { value: projectName, timestamp: Date.now() };
|
|
88
|
+
return projectName;
|
|
89
|
+
} catch {
|
|
90
|
+
return cache.mainProjectName.value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
83
93
|
// src/colors.ts
|
|
84
94
|
var C = {
|
|
85
95
|
RESET: "\x1B[0m",
|
|
@@ -130,28 +140,39 @@ function renderStatusLine(ctx) {
|
|
|
130
140
|
const totalTokens = usage ? usage.input_tokens + usage.output_tokens + usage.cache_creation_input_tokens + usage.cache_read_input_tokens : 0;
|
|
131
141
|
const contextPct = Math.round(totalTokens / contextSize * 100);
|
|
132
142
|
const ctxColor = getUsageColor(contextPct);
|
|
133
|
-
let line1
|
|
143
|
+
let line1;
|
|
144
|
+
if (ctx.mainProjectName) {
|
|
145
|
+
line1 = `${C.WHITE}\uD83D\uDCC1 ${ctx.mainProjectName}${C.RESET} | ${C.WHITE}\uD83C\uDF32 ${folder}${C.RESET}`;
|
|
146
|
+
} else {
|
|
147
|
+
line1 = `${C.WHITE}\uD83D\uDCC1 ${folder}${C.RESET}`;
|
|
148
|
+
}
|
|
134
149
|
if (ctx.branch) {
|
|
135
150
|
line1 += ` | ${C.WHITE}\uD83C\uDF3F ${ctx.branch}${C.RESET}`;
|
|
136
151
|
}
|
|
137
152
|
lines.push(line1);
|
|
138
153
|
const line2 = `${C.WHITE}\u23F1\uFE0F ${formatTime(sessionHrs, sessionMins)}${C.RESET}` + ` | ${C.WHITE}\uD83D\uDCB0 $${costUsd.toFixed(2)}${C.RESET}` + ` | ${ctxColor}\uD83E\uDDE0 ${formatNumber(totalTokens)} (${contextPct}%)${C.RESET}`;
|
|
139
154
|
lines.push(line2);
|
|
140
|
-
if (ctx.
|
|
155
|
+
if (ctx.rateLimits) {
|
|
141
156
|
const parts = [];
|
|
142
|
-
if (ctx.
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
if (ctx.rateLimits.five_hour) {
|
|
158
|
+
const { resets_at, used_percentage } = ctx.rateLimits.five_hour;
|
|
159
|
+
if (resets_at) {
|
|
160
|
+
const resetTime = new Date(resets_at * 1000);
|
|
161
|
+
const h = resetTime.getHours();
|
|
162
|
+
const m = resetTime.getMinutes();
|
|
163
|
+
parts.push(`${C.WHITE}\u23F3 ${formatTime(h, m)}${C.RESET}`);
|
|
164
|
+
}
|
|
165
|
+
const usageColor = getUsageColor(used_percentage);
|
|
166
|
+
parts.push(`${usageColor}\uD83D\uDCCA ${Math.round(used_percentage)}/100${C.RESET}`);
|
|
146
167
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const weekColor = getUsageColor(
|
|
154
|
-
parts.push(`${weekColor}\uD83D\uDCC5 ${Math.round(
|
|
168
|
+
if (ctx.rateLimits.seven_day) {
|
|
169
|
+
const { resets_at, used_percentage } = ctx.rateLimits.seven_day;
|
|
170
|
+
if (resets_at) {
|
|
171
|
+
const resetTime = new Date(resets_at * 1000);
|
|
172
|
+
parts.push(`${C.WHITE}\u23F0 ${formatResetDate(resetTime)}${C.RESET}`);
|
|
173
|
+
}
|
|
174
|
+
const weekColor = getUsageColor(used_percentage);
|
|
175
|
+
parts.push(`${weekColor}\uD83D\uDCC5 ${Math.round(used_percentage)}/100${C.RESET}`);
|
|
155
176
|
}
|
|
156
177
|
if (parts.length > 0) {
|
|
157
178
|
lines.push(parts.join(" | "));
|
|
@@ -183,105 +204,22 @@ async function readStdin() {
|
|
|
183
204
|
return Buffer.concat(chunks).toString("utf-8");
|
|
184
205
|
}
|
|
185
206
|
|
|
186
|
-
// src/usage/token.ts
|
|
187
|
-
var {$: $4 } = globalThis.Bun;
|
|
188
|
-
async function getAccessToken() {
|
|
189
|
-
if (process.platform !== "darwin")
|
|
190
|
-
return null;
|
|
191
|
-
try {
|
|
192
|
-
const result = await $4`security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null`.text();
|
|
193
|
-
const creds = JSON.parse(result);
|
|
194
|
-
return creds?.claudeAiOauth?.accessToken ?? null;
|
|
195
|
-
} catch {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
async function getAccessTokenCached() {
|
|
200
|
-
if (Date.now() - cache.accessToken.timestamp < CACHE_TTL.accessToken) {
|
|
201
|
-
return cache.accessToken.value;
|
|
202
|
-
}
|
|
203
|
-
const token = await getAccessToken();
|
|
204
|
-
cache.accessToken = { value: token, timestamp: Date.now() };
|
|
205
|
-
return token;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/usage/api.ts
|
|
209
|
-
async function fetchUsageFromAPI(accessToken) {
|
|
210
|
-
try {
|
|
211
|
-
const controller = new AbortController;
|
|
212
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
213
|
-
const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
214
|
-
headers: {
|
|
215
|
-
Authorization: `Bearer ${accessToken}`,
|
|
216
|
-
"anthropic-beta": "oauth-2025-04-20"
|
|
217
|
-
},
|
|
218
|
-
signal: controller.signal
|
|
219
|
-
});
|
|
220
|
-
clearTimeout(timeout);
|
|
221
|
-
if (!response.ok)
|
|
222
|
-
return null;
|
|
223
|
-
return await response.json();
|
|
224
|
-
} catch {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
function usageResponseToBlockUsage(data) {
|
|
229
|
-
const result = {
|
|
230
|
-
resetTime: null,
|
|
231
|
-
utilization: 0,
|
|
232
|
-
sevenDayUtilization: null,
|
|
233
|
-
sevenDayResetTime: null
|
|
234
|
-
};
|
|
235
|
-
if (data.five_hour) {
|
|
236
|
-
result.utilization = data.five_hour.utilization;
|
|
237
|
-
if (data.five_hour.resets_at) {
|
|
238
|
-
result.resetTime = new Date(data.five_hour.resets_at);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (data.seven_day) {
|
|
242
|
-
result.sevenDayUtilization = data.seven_day.utilization;
|
|
243
|
-
if (data.seven_day.resets_at) {
|
|
244
|
-
result.sevenDayResetTime = new Date(data.seven_day.resets_at);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return result;
|
|
248
|
-
}
|
|
249
|
-
async function getUsageCached() {
|
|
250
|
-
if (Date.now() - cache.blockUsage.timestamp < CACHE_TTL.blockUsage) {
|
|
251
|
-
return cache.blockUsage.value;
|
|
252
|
-
}
|
|
253
|
-
const token = await getAccessTokenCached();
|
|
254
|
-
if (!token) {
|
|
255
|
-
cache.blockUsage = { value: null, timestamp: Date.now() };
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
const data = await fetchUsageFromAPI(token);
|
|
259
|
-
if (!data) {
|
|
260
|
-
cache.blockUsage = { value: null, timestamp: Date.now() };
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
const blockUsage = usageResponseToBlockUsage(data);
|
|
264
|
-
cache.blockUsage = { value: blockUsage, timestamp: Date.now() };
|
|
265
|
-
return blockUsage;
|
|
266
|
-
}
|
|
267
207
|
// src/index.ts
|
|
268
|
-
async function main(
|
|
269
|
-
const args = cliArgs ?? process.argv.slice(2);
|
|
270
|
-
const { showUsage } = parseCliArgs(args);
|
|
208
|
+
async function main() {
|
|
271
209
|
const claudeJson = JSON.parse(await readStdin() || "{}");
|
|
272
|
-
const [branch, gitChanges, prUrl,
|
|
210
|
+
const [branch, gitChanges, prUrl, mainProjectName] = await Promise.all([
|
|
273
211
|
getBranchCached(),
|
|
274
212
|
getGitChangesCached(),
|
|
275
213
|
getPrUrlCached(),
|
|
276
|
-
|
|
214
|
+
getMainProjectNameCached()
|
|
277
215
|
]);
|
|
278
216
|
const lines = renderStatusLine({
|
|
279
217
|
claudeJson,
|
|
280
218
|
branch,
|
|
281
219
|
gitChanges,
|
|
282
220
|
prUrl,
|
|
283
|
-
|
|
284
|
-
|
|
221
|
+
rateLimits: claudeJson.rate_limits ?? null,
|
|
222
|
+
mainProjectName
|
|
285
223
|
});
|
|
286
224
|
for (const line of lines) {
|
|
287
225
|
console.log(line);
|