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.
@@ -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.38";
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 { execSync as execSync8, spawnSync } from "child_process";
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
- return parsed?.claudeAiOauth?.accessToken ?? null;
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
- if (fs7.existsSync(credFile)) {
54507
- const creds = JSON.parse(fs7.readFileSync(credFile, "utf8"));
54508
- return creds?.claudeAiOauth?.accessToken ?? null;
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
- return null;
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
- if (fs7.existsSync(CACHE_FILE)) {
54521
- const stat = fs7.statSync(CACHE_FILE);
54522
- const fileAge = now - Math.floor(stat.mtimeMs / 1000);
54523
- if (fileAge < CACHE_MAX_AGE) {
54524
- cachedData = JSON.parse(fs7.readFileSync(CACHE_FILE, "utf8"));
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 cachedData;
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
- return null;
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: 5000 });
54544
- if (curlResult.error || curlResult.status !== 0)
54545
- return null;
54546
- const result = curlResult.stdout;
54547
- const data = JSON.parse(result);
54548
- let apiData = {};
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
- return null;
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 (!data || data.sessionUsage === undefined) {
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 (!data || data.weeklyUsage === undefined) {
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 (!data || !data.sessionResetAt) {
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.38",
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
- }
@@ -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