ccstatusline-usage 2.0.38 → 2.0.40
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/dist/ccstatusline.js +119 -42
- package/package.json +2 -6
- package/defaults/settings-enhanced.json +0 -73
- package/scripts/context.sh +0 -58
- package/scripts/setup-enhanced.sh +0 -98
- package/scripts/usage.sh +0 -147
package/dist/ccstatusline.js
CHANGED
|
@@ -51450,7 +51450,7 @@ import { execSync as execSync3 } from "child_process";
|
|
|
51450
51450
|
import * as fs5 from "fs";
|
|
51451
51451
|
import * as path4 from "path";
|
|
51452
51452
|
var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils";
|
|
51453
|
-
var PACKAGE_VERSION = "2.0.
|
|
51453
|
+
var PACKAGE_VERSION = "2.0.40";
|
|
51454
51454
|
function getPackageVersion() {
|
|
51455
51455
|
if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
|
|
51456
51456
|
return PACKAGE_VERSION;
|
|
@@ -54487,49 +54487,99 @@ class ClaudeSessionIdWidget {
|
|
|
54487
54487
|
}
|
|
54488
54488
|
}
|
|
54489
54489
|
// src/widgets/ApiUsage.tsx
|
|
54490
|
-
import {
|
|
54490
|
+
import {
|
|
54491
|
+
execSync as execSync8,
|
|
54492
|
+
spawnSync
|
|
54493
|
+
} from "child_process";
|
|
54491
54494
|
import * as fs7 from "fs";
|
|
54492
54495
|
import * as path6 from "path";
|
|
54493
54496
|
var CACHE_FILE = path6.join(process.env.HOME ?? "", ".cache", "ccstatusline-api.json");
|
|
54497
|
+
var LOCK_FILE = path6.join(process.env.HOME ?? "", ".cache", "ccstatusline-api.lock");
|
|
54494
54498
|
var CACHE_MAX_AGE = 180;
|
|
54499
|
+
var LOCK_MAX_AGE = 30;
|
|
54500
|
+
var TOKEN_CACHE_MAX_AGE = 3600;
|
|
54495
54501
|
var cachedData = null;
|
|
54496
54502
|
var cacheTime = 0;
|
|
54503
|
+
var cachedToken = null;
|
|
54504
|
+
var tokenCacheTime = 0;
|
|
54497
54505
|
function getToken() {
|
|
54506
|
+
const now = Math.floor(Date.now() / 1000);
|
|
54507
|
+
if (cachedToken && now - tokenCacheTime < TOKEN_CACHE_MAX_AGE) {
|
|
54508
|
+
return cachedToken;
|
|
54509
|
+
}
|
|
54498
54510
|
try {
|
|
54499
54511
|
const isMac = process.platform === "darwin";
|
|
54500
54512
|
if (isMac) {
|
|
54501
54513
|
const result = execSync8('security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null', { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
54502
54514
|
const parsed = JSON.parse(result);
|
|
54503
|
-
|
|
54515
|
+
const token = parsed?.claudeAiOauth?.accessToken ?? null;
|
|
54516
|
+
if (token) {
|
|
54517
|
+
cachedToken = token;
|
|
54518
|
+
tokenCacheTime = now;
|
|
54519
|
+
}
|
|
54520
|
+
return token;
|
|
54504
54521
|
} else {
|
|
54505
54522
|
const credFile = path6.join(process.env.HOME ?? "", ".claude", ".credentials.json");
|
|
54506
|
-
|
|
54507
|
-
|
|
54508
|
-
|
|
54523
|
+
const creds = JSON.parse(fs7.readFileSync(credFile, "utf8"));
|
|
54524
|
+
const token = creds?.claudeAiOauth?.accessToken ?? null;
|
|
54525
|
+
if (token) {
|
|
54526
|
+
cachedToken = token;
|
|
54527
|
+
tokenCacheTime = now;
|
|
54509
54528
|
}
|
|
54529
|
+
return token;
|
|
54510
54530
|
}
|
|
54511
|
-
} catch {
|
|
54512
|
-
|
|
54531
|
+
} catch {
|
|
54532
|
+
return null;
|
|
54533
|
+
}
|
|
54534
|
+
}
|
|
54535
|
+
function readStaleCache() {
|
|
54536
|
+
try {
|
|
54537
|
+
return JSON.parse(fs7.readFileSync(CACHE_FILE, "utf8"));
|
|
54538
|
+
} catch {
|
|
54539
|
+
return null;
|
|
54540
|
+
}
|
|
54513
54541
|
}
|
|
54514
54542
|
function fetchApiData() {
|
|
54515
54543
|
const now = Math.floor(Date.now() / 1000);
|
|
54516
|
-
if (cachedData && now - cacheTime < CACHE_MAX_AGE) {
|
|
54544
|
+
if (cachedData && !cachedData.error && now - cacheTime < CACHE_MAX_AGE) {
|
|
54517
54545
|
return cachedData;
|
|
54518
54546
|
}
|
|
54519
54547
|
try {
|
|
54520
|
-
|
|
54521
|
-
|
|
54522
|
-
|
|
54523
|
-
|
|
54524
|
-
|
|
54548
|
+
const stat = fs7.statSync(CACHE_FILE);
|
|
54549
|
+
const fileAge = now - Math.floor(stat.mtimeMs / 1000);
|
|
54550
|
+
if (fileAge < CACHE_MAX_AGE) {
|
|
54551
|
+
const fileData = JSON.parse(fs7.readFileSync(CACHE_FILE, "utf8"));
|
|
54552
|
+
if (!fileData.error) {
|
|
54553
|
+
cachedData = fileData;
|
|
54525
54554
|
cacheTime = now;
|
|
54526
|
-
return
|
|
54555
|
+
return fileData;
|
|
54527
54556
|
}
|
|
54528
54557
|
}
|
|
54529
54558
|
} catch {}
|
|
54559
|
+
try {
|
|
54560
|
+
const lockStat = fs7.statSync(LOCK_FILE);
|
|
54561
|
+
const lockAge = now - Math.floor(lockStat.mtimeMs / 1000);
|
|
54562
|
+
if (lockAge < LOCK_MAX_AGE) {
|
|
54563
|
+
const stale = readStaleCache();
|
|
54564
|
+
if (stale && !stale.error)
|
|
54565
|
+
return stale;
|
|
54566
|
+
return { error: "timeout" };
|
|
54567
|
+
}
|
|
54568
|
+
} catch {}
|
|
54569
|
+
try {
|
|
54570
|
+
const lockDir = path6.dirname(LOCK_FILE);
|
|
54571
|
+
if (!fs7.existsSync(lockDir)) {
|
|
54572
|
+
fs7.mkdirSync(lockDir, { recursive: true });
|
|
54573
|
+
}
|
|
54574
|
+
fs7.writeFileSync(LOCK_FILE, "");
|
|
54575
|
+
} catch {}
|
|
54530
54576
|
const token = getToken();
|
|
54531
|
-
if (!token)
|
|
54532
|
-
|
|
54577
|
+
if (!token) {
|
|
54578
|
+
const stale = readStaleCache();
|
|
54579
|
+
if (stale && !stale.error)
|
|
54580
|
+
return stale;
|
|
54581
|
+
return { error: "no-credentials" };
|
|
54582
|
+
}
|
|
54533
54583
|
try {
|
|
54534
54584
|
const curlResult = spawnSync("curl", [
|
|
54535
54585
|
"-s",
|
|
@@ -54540,12 +54590,15 @@ function fetchApiData() {
|
|
|
54540
54590
|
`Authorization: Bearer ${token}`,
|
|
54541
54591
|
"-H",
|
|
54542
54592
|
"anthropic-beta: oauth-2025-04-20"
|
|
54543
|
-
], { encoding: "utf8", timeout:
|
|
54544
|
-
if (curlResult.error || curlResult.status !== 0)
|
|
54545
|
-
|
|
54546
|
-
|
|
54547
|
-
|
|
54548
|
-
|
|
54593
|
+
], { encoding: "utf8", timeout: 6000 });
|
|
54594
|
+
if (curlResult.error || curlResult.status !== 0 || !curlResult.stdout) {
|
|
54595
|
+
const stale = readStaleCache();
|
|
54596
|
+
if (stale && !stale.error)
|
|
54597
|
+
return stale;
|
|
54598
|
+
return { error: "api-error" };
|
|
54599
|
+
}
|
|
54600
|
+
const data = JSON.parse(curlResult.stdout);
|
|
54601
|
+
const apiData = {};
|
|
54549
54602
|
if (data.five_hour) {
|
|
54550
54603
|
apiData.sessionUsage = data.five_hour.utilization;
|
|
54551
54604
|
apiData.sessionResetAt = data.five_hour.reset_at;
|
|
@@ -54553,6 +54606,12 @@ function fetchApiData() {
|
|
|
54553
54606
|
if (data.seven_day) {
|
|
54554
54607
|
apiData.weeklyUsage = data.seven_day.utilization;
|
|
54555
54608
|
}
|
|
54609
|
+
if (apiData.sessionUsage === undefined && apiData.weeklyUsage === undefined) {
|
|
54610
|
+
const stale = readStaleCache();
|
|
54611
|
+
if (stale && !stale.error)
|
|
54612
|
+
return stale;
|
|
54613
|
+
return { error: "parse-error" };
|
|
54614
|
+
}
|
|
54556
54615
|
try {
|
|
54557
54616
|
const cacheDir = path6.dirname(CACHE_FILE);
|
|
54558
54617
|
if (!fs7.existsSync(cacheDir)) {
|
|
@@ -54564,7 +54623,22 @@ function fetchApiData() {
|
|
|
54564
54623
|
cacheTime = now;
|
|
54565
54624
|
return apiData;
|
|
54566
54625
|
} catch {
|
|
54567
|
-
|
|
54626
|
+
const stale = readStaleCache();
|
|
54627
|
+
if (stale && !stale.error)
|
|
54628
|
+
return stale;
|
|
54629
|
+
return { error: "parse-error" };
|
|
54630
|
+
}
|
|
54631
|
+
}
|
|
54632
|
+
function getErrorMessage(error43) {
|
|
54633
|
+
switch (error43) {
|
|
54634
|
+
case "no-credentials":
|
|
54635
|
+
return "[No credentials]";
|
|
54636
|
+
case "timeout":
|
|
54637
|
+
return "[Timeout]";
|
|
54638
|
+
case "api-error":
|
|
54639
|
+
return "[API Error]";
|
|
54640
|
+
case "parse-error":
|
|
54641
|
+
return "[Parse Error]";
|
|
54568
54642
|
}
|
|
54569
54643
|
}
|
|
54570
54644
|
function makeProgressBar(percent, width = 15) {
|
|
@@ -54583,23 +54657,24 @@ class SessionUsageWidget {
|
|
|
54583
54657
|
getDisplayName() {
|
|
54584
54658
|
return "Session Usage";
|
|
54585
54659
|
}
|
|
54586
|
-
getEditorDisplay() {
|
|
54660
|
+
getEditorDisplay(item) {
|
|
54587
54661
|
return { displayText: this.getDisplayName() };
|
|
54588
54662
|
}
|
|
54589
|
-
render(item, context) {
|
|
54663
|
+
render(item, context, settings) {
|
|
54590
54664
|
if (context.isPreview)
|
|
54591
54665
|
return "Session: [███░░░░░░░░░░░░] 20%";
|
|
54592
54666
|
const data = fetchApiData();
|
|
54593
|
-
if (
|
|
54667
|
+
if (data.error)
|
|
54668
|
+
return getErrorMessage(data.error);
|
|
54669
|
+
if (data.sessionUsage === undefined)
|
|
54594
54670
|
return null;
|
|
54595
|
-
}
|
|
54596
54671
|
const percent = data.sessionUsage;
|
|
54597
54672
|
return `Session: ${makeProgressBar(percent)} ${percent.toFixed(1)}%`;
|
|
54598
54673
|
}
|
|
54599
54674
|
supportsRawValue() {
|
|
54600
54675
|
return false;
|
|
54601
54676
|
}
|
|
54602
|
-
supportsColors() {
|
|
54677
|
+
supportsColors(item) {
|
|
54603
54678
|
return true;
|
|
54604
54679
|
}
|
|
54605
54680
|
}
|
|
@@ -54614,23 +54689,24 @@ class WeeklyUsageWidget {
|
|
|
54614
54689
|
getDisplayName() {
|
|
54615
54690
|
return "Weekly Usage";
|
|
54616
54691
|
}
|
|
54617
|
-
getEditorDisplay() {
|
|
54692
|
+
getEditorDisplay(item) {
|
|
54618
54693
|
return { displayText: this.getDisplayName() };
|
|
54619
54694
|
}
|
|
54620
|
-
render(item, context) {
|
|
54695
|
+
render(item, context, settings) {
|
|
54621
54696
|
if (context.isPreview)
|
|
54622
54697
|
return "Weekly: [██░░░░░░░░░░░░░] 12%";
|
|
54623
54698
|
const data = fetchApiData();
|
|
54624
|
-
if (
|
|
54699
|
+
if (data.error)
|
|
54700
|
+
return getErrorMessage(data.error);
|
|
54701
|
+
if (data.weeklyUsage === undefined)
|
|
54625
54702
|
return null;
|
|
54626
|
-
}
|
|
54627
54703
|
const percent = data.weeklyUsage;
|
|
54628
54704
|
return `Weekly: ${makeProgressBar(percent)} ${percent.toFixed(1)}%`;
|
|
54629
54705
|
}
|
|
54630
54706
|
supportsRawValue() {
|
|
54631
54707
|
return false;
|
|
54632
54708
|
}
|
|
54633
|
-
supportsColors() {
|
|
54709
|
+
supportsColors(item) {
|
|
54634
54710
|
return true;
|
|
54635
54711
|
}
|
|
54636
54712
|
}
|
|
@@ -54645,16 +54721,17 @@ class ResetTimerWidget {
|
|
|
54645
54721
|
getDisplayName() {
|
|
54646
54722
|
return "Reset Timer";
|
|
54647
54723
|
}
|
|
54648
|
-
getEditorDisplay() {
|
|
54724
|
+
getEditorDisplay(item) {
|
|
54649
54725
|
return { displayText: this.getDisplayName() };
|
|
54650
54726
|
}
|
|
54651
|
-
render(item, context) {
|
|
54727
|
+
render(item, context, settings) {
|
|
54652
54728
|
if (context.isPreview)
|
|
54653
54729
|
return "4:30 hr";
|
|
54654
54730
|
const data = fetchApiData();
|
|
54655
|
-
if (
|
|
54731
|
+
if (data.error)
|
|
54732
|
+
return getErrorMessage(data.error);
|
|
54733
|
+
if (!data.sessionResetAt)
|
|
54656
54734
|
return null;
|
|
54657
|
-
}
|
|
54658
54735
|
try {
|
|
54659
54736
|
const resetTime = new Date(data.sessionResetAt).getTime();
|
|
54660
54737
|
const now = Date.now();
|
|
@@ -54671,7 +54748,7 @@ class ResetTimerWidget {
|
|
|
54671
54748
|
supportsRawValue() {
|
|
54672
54749
|
return false;
|
|
54673
54750
|
}
|
|
54674
|
-
supportsColors() {
|
|
54751
|
+
supportsColors(item) {
|
|
54675
54752
|
return true;
|
|
54676
54753
|
}
|
|
54677
54754
|
}
|
|
@@ -54686,10 +54763,10 @@ class ContextBarWidget {
|
|
|
54686
54763
|
getDisplayName() {
|
|
54687
54764
|
return "Context Bar";
|
|
54688
54765
|
}
|
|
54689
|
-
getEditorDisplay() {
|
|
54766
|
+
getEditorDisplay(item) {
|
|
54690
54767
|
return { displayText: this.getDisplayName() };
|
|
54691
54768
|
}
|
|
54692
|
-
render(item, context) {
|
|
54769
|
+
render(item, context, settings) {
|
|
54693
54770
|
if (context.isPreview)
|
|
54694
54771
|
return "Context: [████░░░░░░░░░░░] 50k/200k (25%)";
|
|
54695
54772
|
const cw = context.data?.context_window;
|
|
@@ -54713,7 +54790,7 @@ class ContextBarWidget {
|
|
|
54713
54790
|
supportsRawValue() {
|
|
54714
54791
|
return false;
|
|
54715
54792
|
}
|
|
54716
|
-
supportsColors() {
|
|
54793
|
+
supportsColors(item) {
|
|
54717
54794
|
return true;
|
|
54718
54795
|
}
|
|
54719
54796
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccstatusline-usage",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.40",
|
|
4
4
|
"description": "A customizable status line formatter for Claude Code CLI",
|
|
5
5
|
"module": "src/ccstatusline.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -8,11 +8,7 @@
|
|
|
8
8
|
"ccstatusline": "dist/ccstatusline.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist/"
|
|
12
|
-
"scripts/usage.sh",
|
|
13
|
-
"scripts/context.sh",
|
|
14
|
-
"scripts/setup-enhanced.sh",
|
|
15
|
-
"defaults/"
|
|
11
|
+
"dist/"
|
|
16
12
|
],
|
|
17
13
|
"scripts": {
|
|
18
14
|
"start": "bun run src/ccstatusline.ts",
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"lines": [
|
|
4
|
-
[
|
|
5
|
-
{
|
|
6
|
-
"id": "session-usage",
|
|
7
|
-
"type": "custom-command",
|
|
8
|
-
"color": "brightBlue",
|
|
9
|
-
"commandPath": "scripts/usage.sh session"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"id": "sep-session-weekly",
|
|
13
|
-
"type": "separator"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": "weekly-usage",
|
|
17
|
-
"type": "custom-command",
|
|
18
|
-
"color": "brightBlue",
|
|
19
|
-
"commandPath": "scripts/usage.sh weekly"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"id": "sep-weekly-reset",
|
|
23
|
-
"type": "separator"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": "reset-timer",
|
|
27
|
-
"type": "custom-command",
|
|
28
|
-
"color": "brightBlue",
|
|
29
|
-
"commandPath": "scripts/usage.sh reset"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"id": "sep-reset-model",
|
|
33
|
-
"type": "separator"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"id": "model",
|
|
37
|
-
"type": "model",
|
|
38
|
-
"color": "magenta"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"id": "sep-model-chatid",
|
|
42
|
-
"type": "separator"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"id": "chat-id",
|
|
46
|
-
"type": "claude-session-id",
|
|
47
|
-
"color": "cyan"
|
|
48
|
-
}
|
|
49
|
-
],
|
|
50
|
-
[
|
|
51
|
-
{
|
|
52
|
-
"id": "context-usage",
|
|
53
|
-
"type": "custom-command",
|
|
54
|
-
"color": "blue",
|
|
55
|
-
"commandPath": "scripts/context.sh"
|
|
56
|
-
}
|
|
57
|
-
],
|
|
58
|
-
[]
|
|
59
|
-
],
|
|
60
|
-
"flexMode": "full-minus-40",
|
|
61
|
-
"compactThreshold": 60,
|
|
62
|
-
"colorLevel": 2,
|
|
63
|
-
"inheritSeparatorColors": false,
|
|
64
|
-
"globalBold": false,
|
|
65
|
-
"powerline": {
|
|
66
|
-
"enabled": false,
|
|
67
|
-
"separators": [""],
|
|
68
|
-
"separatorInvertBackground": [false],
|
|
69
|
-
"startCaps": [],
|
|
70
|
-
"endCaps": [],
|
|
71
|
-
"autoAlign": false
|
|
72
|
-
}
|
|
73
|
-
}
|
package/scripts/context.sh
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
CACHE_FILE="$HOME/.claude/.statusline/context.json"
|
|
4
|
-
|
|
5
|
-
make_bar() {
|
|
6
|
-
local pct="$1"
|
|
7
|
-
local width=15
|
|
8
|
-
local filled=$((pct * width / 100))
|
|
9
|
-
local empty=$((width - filled))
|
|
10
|
-
printf "["
|
|
11
|
-
printf "█%.0s" $(seq 1 "$filled")
|
|
12
|
-
printf "░%.0s" $(seq 1 "$empty")
|
|
13
|
-
printf "]"
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
format_tokens() {
|
|
17
|
-
local tokens="$1"
|
|
18
|
-
if [[ $tokens -ge 1000 ]]; then
|
|
19
|
-
echo "$((tokens / 1000))k"
|
|
20
|
-
else
|
|
21
|
-
echo "$tokens"
|
|
22
|
-
fi
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if [[ ! -f "$CACHE_FILE" ]]; then
|
|
26
|
-
BAR=$(make_bar 0)
|
|
27
|
-
echo "Context: $BAR 0k/200k (0%)"
|
|
28
|
-
exit 0
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
INPUT=$(cat "$CACHE_FILE")
|
|
32
|
-
|
|
33
|
-
MAX_TOKENS=$(echo "$INPUT" | jq -r '.context_window_size // empty')
|
|
34
|
-
CURRENT_USAGE=$(echo "$INPUT" | jq -r '.current_usage // empty')
|
|
35
|
-
|
|
36
|
-
if [[ -z "$MAX_TOKENS" || "$MAX_TOKENS" == "null" || -z "$CURRENT_USAGE" || "$CURRENT_USAGE" == "null" ]]; then
|
|
37
|
-
BAR=$(make_bar 0)
|
|
38
|
-
echo "Context: $BAR 0k/200k (0%)"
|
|
39
|
-
exit 0
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
INPUT_TOKENS=$(echo "$INPUT" | jq -r '.current_usage.input_tokens // 0')
|
|
43
|
-
CACHE_CREATE=$(echo "$INPUT" | jq -r '.current_usage.cache_creation_input_tokens // 0')
|
|
44
|
-
CACHE_READ=$(echo "$INPUT" | jq -r '.current_usage.cache_read_input_tokens // 0')
|
|
45
|
-
|
|
46
|
-
CURRENT_TOKENS=$((INPUT_TOKENS + CACHE_CREATE + CACHE_READ))
|
|
47
|
-
|
|
48
|
-
if [[ $MAX_TOKENS -gt 0 ]]; then
|
|
49
|
-
PERCENT=$((CURRENT_TOKENS * 100 / MAX_TOKENS))
|
|
50
|
-
else
|
|
51
|
-
PERCENT=0
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
CURRENT_FMT=$(format_tokens "$CURRENT_TOKENS")
|
|
55
|
-
MAX_FMT=$(format_tokens "$MAX_TOKENS")
|
|
56
|
-
BAR=$(make_bar "$PERCENT")
|
|
57
|
-
|
|
58
|
-
echo "Context: $BAR ${CURRENT_FMT}/${MAX_FMT} (${PERCENT}%)"
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Setup script for ccstatusline-usage enhanced configuration
|
|
3
|
-
# Installs usage scripts and applies enhanced settings
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR="$HOME/.local/share/ccstatusline"
|
|
8
|
-
CONFIG_DIR="$HOME/.config/ccstatusline"
|
|
9
|
-
SETTINGS_FILE="$CONFIG_DIR/settings.json"
|
|
10
|
-
|
|
11
|
-
# Find the package directory (where this script is located)
|
|
12
|
-
PKG_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
13
|
-
|
|
14
|
-
echo "Setting up ccstatusline-usage enhanced configuration..."
|
|
15
|
-
|
|
16
|
-
# Create directories
|
|
17
|
-
mkdir -p "$SCRIPT_DIR"
|
|
18
|
-
mkdir -p "$CONFIG_DIR"
|
|
19
|
-
|
|
20
|
-
# Copy scripts
|
|
21
|
-
if [[ -f "$PKG_DIR/scripts/usage.sh" ]]; then
|
|
22
|
-
cp "$PKG_DIR/scripts/usage.sh" "$SCRIPT_DIR/"
|
|
23
|
-
chmod +x "$SCRIPT_DIR/usage.sh"
|
|
24
|
-
echo "✓ Installed usage.sh to $SCRIPT_DIR/"
|
|
25
|
-
else
|
|
26
|
-
echo "✗ usage.sh not found in package"
|
|
27
|
-
exit 1
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
if [[ -f "$PKG_DIR/scripts/context.sh" ]]; then
|
|
31
|
-
cp "$PKG_DIR/scripts/context.sh" "$SCRIPT_DIR/"
|
|
32
|
-
chmod +x "$SCRIPT_DIR/context.sh"
|
|
33
|
-
echo "✓ Installed context.sh to $SCRIPT_DIR/"
|
|
34
|
-
else
|
|
35
|
-
echo "✗ context.sh not found in package"
|
|
36
|
-
exit 1
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# Create enhanced settings with correct paths
|
|
40
|
-
cat > "$SETTINGS_FILE" << 'EOF'
|
|
41
|
-
{
|
|
42
|
-
"version": 3,
|
|
43
|
-
"lines": [
|
|
44
|
-
[
|
|
45
|
-
{
|
|
46
|
-
"id": "session-usage",
|
|
47
|
-
"type": "custom-command",
|
|
48
|
-
"color": "brightBlue",
|
|
49
|
-
"commandPath": "$HOME/.local/share/ccstatusline/usage.sh session",
|
|
50
|
-
"timeout": 5000
|
|
51
|
-
},
|
|
52
|
-
{"id": "sep1", "type": "separator"},
|
|
53
|
-
{
|
|
54
|
-
"id": "weekly-usage",
|
|
55
|
-
"type": "custom-command",
|
|
56
|
-
"color": "brightBlue",
|
|
57
|
-
"commandPath": "$HOME/.local/share/ccstatusline/usage.sh weekly",
|
|
58
|
-
"timeout": 5000
|
|
59
|
-
},
|
|
60
|
-
{"id": "sep2", "type": "separator"},
|
|
61
|
-
{
|
|
62
|
-
"id": "reset-timer",
|
|
63
|
-
"type": "custom-command",
|
|
64
|
-
"color": "brightBlue",
|
|
65
|
-
"commandPath": "$HOME/.local/share/ccstatusline/usage.sh reset",
|
|
66
|
-
"timeout": 5000
|
|
67
|
-
},
|
|
68
|
-
{"id": "sep3", "type": "separator"},
|
|
69
|
-
{"id": "model", "type": "model", "color": "magenta"},
|
|
70
|
-
{"id": "sep4", "type": "separator"},
|
|
71
|
-
{"id": "session-id", "type": "claude-session-id", "color": "cyan"}
|
|
72
|
-
],
|
|
73
|
-
[
|
|
74
|
-
{
|
|
75
|
-
"id": "context-usage",
|
|
76
|
-
"type": "custom-command",
|
|
77
|
-
"color": "blue",
|
|
78
|
-
"commandPath": "$HOME/.local/share/ccstatusline/context.sh",
|
|
79
|
-
"timeout": 5000
|
|
80
|
-
}
|
|
81
|
-
],
|
|
82
|
-
[]
|
|
83
|
-
],
|
|
84
|
-
"flexMode": "full-minus-40",
|
|
85
|
-
"compactThreshold": 60,
|
|
86
|
-
"colorLevel": 2
|
|
87
|
-
}
|
|
88
|
-
EOF
|
|
89
|
-
|
|
90
|
-
# Expand $HOME in the settings file
|
|
91
|
-
sed -i.bak "s|\$HOME|$HOME|g" "$SETTINGS_FILE" && rm -f "$SETTINGS_FILE.bak"
|
|
92
|
-
|
|
93
|
-
echo "✓ Created enhanced settings at $SETTINGS_FILE"
|
|
94
|
-
echo ""
|
|
95
|
-
echo "Setup complete! Run 'npx ccstatusline-usage' to configure."
|
|
96
|
-
echo ""
|
|
97
|
-
echo "Note: The usage widgets require Anthropic API access."
|
|
98
|
-
echo "Make sure you have valid credentials in ~/.claude/credentials.json"
|
package/scripts/usage.sh
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Cross-platform usage script for ccstatusline-usage
|
|
4
|
-
# Works on both macOS and Linux
|
|
5
|
-
|
|
6
|
-
CACHE_FILE="$HOME/.cache/ccstatusline-api.json"
|
|
7
|
-
LOCK_FILE="$HOME/.cache/ccstatusline-api.lock"
|
|
8
|
-
|
|
9
|
-
# Detect OS for platform-specific commands
|
|
10
|
-
is_macos() {
|
|
11
|
-
[[ "$(uname)" == "Darwin" ]]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
# Get file modification time (seconds since epoch)
|
|
15
|
-
get_mtime() {
|
|
16
|
-
if is_macos; then
|
|
17
|
-
stat -f '%m' "$1" 2>/dev/null
|
|
18
|
-
else
|
|
19
|
-
stat -c '%Y' "$1" 2>/dev/null
|
|
20
|
-
fi
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Get OAuth token from credentials
|
|
24
|
-
get_token() {
|
|
25
|
-
if is_macos; then
|
|
26
|
-
# macOS: read from keychain
|
|
27
|
-
security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty'
|
|
28
|
-
else
|
|
29
|
-
# Linux: read from credentials file
|
|
30
|
-
jq -r '.claudeAiOauth.accessToken // empty' ~/.claude/.credentials.json 2>/dev/null
|
|
31
|
-
fi
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Parse ISO date to epoch
|
|
35
|
-
parse_iso_date() {
|
|
36
|
-
local iso_date="$1"
|
|
37
|
-
# Remove fractional seconds and Z suffix
|
|
38
|
-
local clean_date="${iso_date%%.*}"
|
|
39
|
-
clean_date="${clean_date%%Z}"
|
|
40
|
-
|
|
41
|
-
if is_macos; then
|
|
42
|
-
TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$clean_date" "+%s" 2>/dev/null
|
|
43
|
-
else
|
|
44
|
-
# Linux: use date -d with ISO format
|
|
45
|
-
date -d "$clean_date" "+%s" 2>/dev/null
|
|
46
|
-
fi
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
fetch_api() {
|
|
50
|
-
local NOW=$(date +%s)
|
|
51
|
-
|
|
52
|
-
# Use cache if < 180 seconds old
|
|
53
|
-
if [[ -f "$CACHE_FILE" ]]; then
|
|
54
|
-
local MTIME=$(get_mtime "$CACHE_FILE")
|
|
55
|
-
if [[ -n "$MTIME" ]]; then
|
|
56
|
-
local CACHE_AGE=$((NOW - MTIME))
|
|
57
|
-
[[ $CACHE_AGE -lt 180 ]] && return 0
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# Rate limit: only try API once per 30 seconds
|
|
62
|
-
if [[ -f "$LOCK_FILE" ]]; then
|
|
63
|
-
local LOCK_MTIME=$(get_mtime "$LOCK_FILE")
|
|
64
|
-
if [[ -n "$LOCK_MTIME" ]]; then
|
|
65
|
-
local LOCK_AGE=$((NOW - LOCK_MTIME))
|
|
66
|
-
if [[ $LOCK_AGE -lt 30 ]]; then
|
|
67
|
-
[[ -f "$CACHE_FILE" ]] && return 0
|
|
68
|
-
return 1
|
|
69
|
-
fi
|
|
70
|
-
fi
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
touch "$LOCK_FILE"
|
|
74
|
-
|
|
75
|
-
TOKEN=$(get_token)
|
|
76
|
-
[[ -z "$TOKEN" ]] && return 1
|
|
77
|
-
|
|
78
|
-
RESPONSE=$(curl -s --max-time 5 "https://api.anthropic.com/api/oauth/usage" \
|
|
79
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
80
|
-
-H "anthropic-beta: oauth-2025-04-20" 2>/dev/null)
|
|
81
|
-
[[ -z "$RESPONSE" ]] && return 1
|
|
82
|
-
|
|
83
|
-
# Ensure cache directory exists
|
|
84
|
-
mkdir -p "$(dirname "$CACHE_FILE")"
|
|
85
|
-
echo "$RESPONSE" > "$CACHE_FILE"
|
|
86
|
-
return 0
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
make_bar() {
|
|
90
|
-
local pct="$1"
|
|
91
|
-
local width=15
|
|
92
|
-
local filled=$((pct * width / 100))
|
|
93
|
-
local empty=$((width - filled))
|
|
94
|
-
printf "["
|
|
95
|
-
printf "█%.0s" $(seq 1 "$filled")
|
|
96
|
-
printf "░%.0s" $(seq 1 "$empty")
|
|
97
|
-
printf "]"
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
MODE="${1:-all}"
|
|
101
|
-
|
|
102
|
-
fetch_api || { echo "[API Error]"; exit 1; }
|
|
103
|
-
|
|
104
|
-
case "$MODE" in
|
|
105
|
-
session)
|
|
106
|
-
SESSION=$(jq -r '.five_hour.utilization // empty' "$CACHE_FILE" 2>/dev/null)
|
|
107
|
-
[[ -z "$SESSION" ]] && { echo "[Parse Error]"; exit 1; }
|
|
108
|
-
SESSION_INT=${SESSION%.*}
|
|
109
|
-
SESSION_BAR=$(make_bar "$SESSION_INT")
|
|
110
|
-
echo "Session: $SESSION_BAR ${SESSION}%"
|
|
111
|
-
;;
|
|
112
|
-
weekly)
|
|
113
|
-
WEEKLY=$(jq -r '.seven_day.utilization // empty' "$CACHE_FILE" 2>/dev/null)
|
|
114
|
-
[[ -z "$WEEKLY" ]] && { echo "[Parse Error]"; exit 1; }
|
|
115
|
-
WEEKLY_INT=${WEEKLY%.*}
|
|
116
|
-
WEEKLY_BAR=$(make_bar "$WEEKLY_INT")
|
|
117
|
-
echo "Weekly: $WEEKLY_BAR ${WEEKLY}%"
|
|
118
|
-
;;
|
|
119
|
-
reset)
|
|
120
|
-
RESETS_AT=$(jq -r '.five_hour.resets_at // empty' "$CACHE_FILE" 2>/dev/null)
|
|
121
|
-
[[ -z "$RESETS_AT" ]] && { echo "[Parse Error]"; exit 1; }
|
|
122
|
-
RESET_EPOCH=$(parse_iso_date "$RESETS_AT")
|
|
123
|
-
NOW_EPOCH=$(date -u +%s)
|
|
124
|
-
if [[ -z "$RESET_EPOCH" ]]; then
|
|
125
|
-
echo "[Date Error]"
|
|
126
|
-
exit 1
|
|
127
|
-
fi
|
|
128
|
-
DIFF=$((RESET_EPOCH - NOW_EPOCH))
|
|
129
|
-
if [[ $DIFF -le 0 ]]; then
|
|
130
|
-
echo "Reset now"
|
|
131
|
-
else
|
|
132
|
-
HOURS=$((DIFF / 3600))
|
|
133
|
-
MINUTES=$(((DIFF % 3600) / 60))
|
|
134
|
-
echo "${HOURS}:$(printf '%02d' $MINUTES) hr"
|
|
135
|
-
fi
|
|
136
|
-
;;
|
|
137
|
-
*)
|
|
138
|
-
SESSION=$(jq -r '.five_hour.utilization // empty' "$CACHE_FILE" 2>/dev/null)
|
|
139
|
-
WEEKLY=$(jq -r '.seven_day.utilization // empty' "$CACHE_FILE" 2>/dev/null)
|
|
140
|
-
[[ -z "$SESSION" || -z "$WEEKLY" ]] && { echo "[Parse Error]"; exit 1; }
|
|
141
|
-
SESSION_INT=${SESSION%.*}
|
|
142
|
-
WEEKLY_INT=${WEEKLY%.*}
|
|
143
|
-
SESSION_BAR=$(make_bar "$SESSION_INT")
|
|
144
|
-
WEEKLY_BAR=$(make_bar "$WEEKLY_INT")
|
|
145
|
-
echo "Session: $SESSION_BAR ${SESSION}% | Weekly: $WEEKLY_BAR ${WEEKLY}%"
|
|
146
|
-
;;
|
|
147
|
-
esac
|