claude-relay 2.2.1 → 2.2.2
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/lib/project.js +0 -9
- package/lib/public/app.js +1 -146
- package/lib/public/css/menus.css +18 -102
- package/lib/public/index.html +5 -32
- package/lib/public/modules/notifications.js +0 -9
- package/package.json +1 -1
- package/lib/usage.js +0 -90
package/lib/project.js
CHANGED
|
@@ -4,7 +4,6 @@ var { createSessionManager } = require("./sessions");
|
|
|
4
4
|
var { createSDKBridge } = require("./sdk-bridge");
|
|
5
5
|
var { createTerminalManager } = require("./terminal-manager");
|
|
6
6
|
var { fetchLatestVersion, isNewer } = require("./updater");
|
|
7
|
-
var { fetchUsageData } = require("./usage");
|
|
8
7
|
var { execFileSync } = require("child_process");
|
|
9
8
|
|
|
10
9
|
// SDK loaded dynamically (ESM module)
|
|
@@ -317,14 +316,6 @@ function createProjectContext(opts) {
|
|
|
317
316
|
return;
|
|
318
317
|
}
|
|
319
318
|
|
|
320
|
-
if (msg.type === "get_usage") {
|
|
321
|
-
fetchUsageData().then(function (data) {
|
|
322
|
-
sendTo(ws, { type: "usage_data", data: data });
|
|
323
|
-
}).catch(function (err) {
|
|
324
|
-
sendTo(ws, { type: "usage_data", error: err.message || "Failed to fetch usage data" });
|
|
325
|
-
});
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
319
|
|
|
329
320
|
if (msg.type === "set_model" && msg.model) {
|
|
330
321
|
var session = sm.getActiveSession();
|
package/lib/public/app.js
CHANGED
|
@@ -502,8 +502,6 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
502
502
|
connectOverlay.classList.add("hidden");
|
|
503
503
|
stopVerbCycle();
|
|
504
504
|
updateFavicon("#57AB5A");
|
|
505
|
-
if (usageFab) usageFab.classList.remove("hidden");
|
|
506
|
-
if (usageHeaderBtn) usageHeaderBtn.classList.remove("hidden");
|
|
507
505
|
} else if (status === "processing") {
|
|
508
506
|
statusDot.classList.add("processing");
|
|
509
507
|
processing = true;
|
|
@@ -605,8 +603,6 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
605
603
|
// --- Usage panel ---
|
|
606
604
|
var usagePanel = $("usage-panel");
|
|
607
605
|
var usagePanelClose = $("usage-panel-close");
|
|
608
|
-
var usageFab = $("usage-fab");
|
|
609
|
-
var usageHeaderBtn = $("usage-header-btn");
|
|
610
606
|
var usageCostEl = $("usage-cost");
|
|
611
607
|
var usageInputEl = $("usage-input");
|
|
612
608
|
var usageOutputEl = $("usage-output");
|
|
@@ -615,126 +611,12 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
615
611
|
var usageTurnsEl = $("usage-turns");
|
|
616
612
|
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
617
613
|
|
|
618
|
-
// Rate limit bar elements
|
|
619
|
-
var usageLoading = $("usage-loading");
|
|
620
|
-
var usageError = $("usage-error");
|
|
621
|
-
var usageBars = $("usage-bars");
|
|
622
|
-
var usageRateLimitFetched = false;
|
|
623
|
-
|
|
624
614
|
function formatTokens(n) {
|
|
625
615
|
if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
|
|
626
616
|
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
|
|
627
617
|
return String(n);
|
|
628
618
|
}
|
|
629
619
|
|
|
630
|
-
function formatTimeUntil(isoStr) {
|
|
631
|
-
if (!isoStr) return "";
|
|
632
|
-
var d = new Date(isoStr);
|
|
633
|
-
var now = Date.now();
|
|
634
|
-
var diff = d.getTime() - now;
|
|
635
|
-
if (diff <= 0) return "Resets soon";
|
|
636
|
-
var hours = Math.floor(diff / 3600000);
|
|
637
|
-
var minutes = Math.floor((diff % 3600000) / 60000);
|
|
638
|
-
var relative;
|
|
639
|
-
if (hours > 24) {
|
|
640
|
-
var days = Math.floor(hours / 24);
|
|
641
|
-
relative = days + "d " + (hours % 24) + "h";
|
|
642
|
-
} else if (hours > 0) {
|
|
643
|
-
relative = hours + "h " + minutes + "m";
|
|
644
|
-
} else {
|
|
645
|
-
relative = minutes + "m";
|
|
646
|
-
}
|
|
647
|
-
// Absolute time: "Mon 2/17 15:00" or "15:00" if today
|
|
648
|
-
var nowDate = new Date(now);
|
|
649
|
-
var month = d.getMonth() + 1;
|
|
650
|
-
var day = d.getDate();
|
|
651
|
-
var hh = String(d.getHours()).padStart(2, "0");
|
|
652
|
-
var mm = String(d.getMinutes()).padStart(2, "0");
|
|
653
|
-
var sameDay = d.getFullYear() === nowDate.getFullYear() && d.getMonth() === nowDate.getMonth() && d.getDate() === nowDate.getDate();
|
|
654
|
-
var abs = sameDay ? hh + ":" + mm : month + "/" + day + " " + hh + ":" + mm;
|
|
655
|
-
return "Resets in " + relative + " (" + abs + ")";
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
function updateRateLimitBar(prefix, utilization, resetsAt) {
|
|
659
|
-
var pctEl = $("usage-pct-" + prefix);
|
|
660
|
-
var fillEl = $("usage-fill-" + prefix);
|
|
661
|
-
var resetEl = $("usage-reset-" + prefix);
|
|
662
|
-
var groupEl = $("usage-bar-" + prefix);
|
|
663
|
-
if (!pctEl || !fillEl || !resetEl) return;
|
|
664
|
-
|
|
665
|
-
if (utilization == null) {
|
|
666
|
-
if (groupEl) groupEl.classList.add("hidden");
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
if (groupEl) groupEl.classList.remove("hidden");
|
|
670
|
-
|
|
671
|
-
var pct = Math.max(0, Math.min(100, Math.round(utilization)));
|
|
672
|
-
pctEl.textContent = pct + "%";
|
|
673
|
-
fillEl.style.width = pct + "%";
|
|
674
|
-
fillEl.className = "usage-bar-fill";
|
|
675
|
-
if (prefix === "extra") fillEl.classList.add("usage-bar-fill-extra");
|
|
676
|
-
if (pct >= 90) fillEl.classList.add("critical");
|
|
677
|
-
else if (pct >= 70) fillEl.classList.add("warn");
|
|
678
|
-
|
|
679
|
-
resetEl.textContent = formatTimeUntil(resetsAt);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function handleUsageData(msg) {
|
|
683
|
-
if (!usageLoading || !usageError || !usageBars) return;
|
|
684
|
-
usageLoading.style.display = "none";
|
|
685
|
-
|
|
686
|
-
if (msg.error) {
|
|
687
|
-
usageError.textContent = msg.error;
|
|
688
|
-
usageError.classList.remove("hidden");
|
|
689
|
-
usageBars.classList.add("hidden");
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
usageError.classList.add("hidden");
|
|
694
|
-
usageBars.classList.remove("hidden");
|
|
695
|
-
var data = msg.data || {};
|
|
696
|
-
|
|
697
|
-
// Session (five_hour)
|
|
698
|
-
var session = data.five_hour || {};
|
|
699
|
-
updateRateLimitBar("session", session.utilization, session.resets_at);
|
|
700
|
-
|
|
701
|
-
// Weekly all models (seven_day)
|
|
702
|
-
var weekly = data.seven_day || {};
|
|
703
|
-
updateRateLimitBar("weekly", weekly.utilization, weekly.resets_at);
|
|
704
|
-
|
|
705
|
-
// Weekly Sonnet only (seven_day_sonnet)
|
|
706
|
-
var sonnet = data.seven_day_sonnet || {};
|
|
707
|
-
updateRateLimitBar("sonnet", sonnet.utilization, sonnet.resets_at);
|
|
708
|
-
|
|
709
|
-
// Extra usage
|
|
710
|
-
var extra = data.extra_usage || {};
|
|
711
|
-
var extraGroup = $("usage-bar-extra");
|
|
712
|
-
if (extra.is_enabled) {
|
|
713
|
-
if (extraGroup) extraGroup.classList.remove("hidden");
|
|
714
|
-
// Compute reset time as first of next month
|
|
715
|
-
var now = new Date();
|
|
716
|
-
var nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
717
|
-
updateRateLimitBar("extra", extra.utilization, nextMonth.toISOString());
|
|
718
|
-
var extraResetEl = $("usage-reset-extra");
|
|
719
|
-
if (extraResetEl && extra.monthly_limit != null) {
|
|
720
|
-
var usedDollars = (extra.used_credits / 100).toFixed(2);
|
|
721
|
-
var limitDollars = (extra.monthly_limit / 100).toFixed(2);
|
|
722
|
-
extraResetEl.textContent = "$" + usedDollars + " / $" + limitDollars;
|
|
723
|
-
}
|
|
724
|
-
} else {
|
|
725
|
-
if (extraGroup) extraGroup.classList.add("hidden");
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
usageRateLimitFetched = true;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
function requestUsageData() {
|
|
732
|
-
if (!ws || ws.readyState !== 1) return;
|
|
733
|
-
if (usageLoading) usageLoading.style.display = "";
|
|
734
|
-
if (usageError) usageError.classList.add("hidden");
|
|
735
|
-
ws.send(JSON.stringify({ type: "get_usage" }));
|
|
736
|
-
}
|
|
737
|
-
|
|
738
620
|
function updateUsagePanel() {
|
|
739
621
|
if (!usageCostEl) return;
|
|
740
622
|
usageCostEl.textContent = "$" + sessionUsage.cost.toFixed(4);
|
|
@@ -761,40 +643,17 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
761
643
|
sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
762
644
|
updateUsagePanel();
|
|
763
645
|
if (usagePanel) usagePanel.classList.add("hidden");
|
|
764
|
-
if (usageFab) usageFab.classList.remove("active");
|
|
765
|
-
if (usageHeaderBtn) usageHeaderBtn.classList.remove("active");
|
|
766
|
-
usageRateLimitFetched = false;
|
|
767
646
|
}
|
|
768
647
|
|
|
769
648
|
function toggleUsagePanel() {
|
|
770
649
|
if (!usagePanel) return;
|
|
771
|
-
|
|
772
|
-
if (usageFab) usageFab.classList.toggle("active", !isHidden);
|
|
773
|
-
if (usageHeaderBtn) usageHeaderBtn.classList.toggle("active", !isHidden);
|
|
774
|
-
// Fetch rate limit data when opening
|
|
775
|
-
if (!isHidden) {
|
|
776
|
-
requestUsageData();
|
|
777
|
-
}
|
|
650
|
+
usagePanel.classList.toggle("hidden");
|
|
778
651
|
refreshIcons();
|
|
779
652
|
}
|
|
780
653
|
|
|
781
654
|
if (usagePanelClose) {
|
|
782
655
|
usagePanelClose.addEventListener("click", function () {
|
|
783
656
|
usagePanel.classList.add("hidden");
|
|
784
|
-
if (usageFab) usageFab.classList.remove("active");
|
|
785
|
-
if (usageHeaderBtn) usageHeaderBtn.classList.remove("active");
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
if (usageFab) {
|
|
790
|
-
usageFab.addEventListener("click", function () {
|
|
791
|
-
toggleUsagePanel();
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
if (usageHeaderBtn) {
|
|
796
|
-
usageHeaderBtn.addEventListener("click", function () {
|
|
797
|
-
toggleUsagePanel();
|
|
798
657
|
});
|
|
799
658
|
}
|
|
800
659
|
|
|
@@ -1195,10 +1054,6 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
1195
1054
|
updateModelSelector(msg.model, msg.models || []);
|
|
1196
1055
|
break;
|
|
1197
1056
|
|
|
1198
|
-
case "usage_data":
|
|
1199
|
-
handleUsageData(msg);
|
|
1200
|
-
break;
|
|
1201
|
-
|
|
1202
1057
|
case "client_count":
|
|
1203
1058
|
var countEl = document.getElementById("client-count");
|
|
1204
1059
|
if (countEl) {
|
package/lib/public/css/menus.css
CHANGED
|
@@ -85,24 +85,6 @@
|
|
|
85
85
|
flex-shrink: 0;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
/* --- Usage header button (mobile only) --- */
|
|
89
|
-
#usage-header-btn {
|
|
90
|
-
display: none;
|
|
91
|
-
align-items: center;
|
|
92
|
-
justify-content: center;
|
|
93
|
-
background: none;
|
|
94
|
-
border: 1px solid transparent;
|
|
95
|
-
border-radius: 8px;
|
|
96
|
-
color: var(--text-dimmer);
|
|
97
|
-
cursor: pointer;
|
|
98
|
-
padding: 4px;
|
|
99
|
-
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
100
|
-
}
|
|
101
|
-
#usage-header-btn .lucide { width: 15px; height: 15px; }
|
|
102
|
-
#usage-header-btn:hover { color: var(--text-secondary); background: rgba(255,255,255,0.04); border-color: var(--border); }
|
|
103
|
-
#usage-header-btn.active { color: var(--accent); border-color: var(--accent); }
|
|
104
|
-
#usage-header-btn.hidden { display: none !important; }
|
|
105
|
-
|
|
106
88
|
/* --- Terminal toggle button --- */
|
|
107
89
|
#terminal-toggle-btn {
|
|
108
90
|
display: flex;
|
|
@@ -485,68 +467,29 @@
|
|
|
485
467
|
padding: 8px 14px 12px;
|
|
486
468
|
}
|
|
487
469
|
|
|
488
|
-
/* ---
|
|
489
|
-
.usage-
|
|
490
|
-
color: var(--text-muted);
|
|
491
|
-
padding: 8px 0;
|
|
492
|
-
text-align: center;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.usage-error {
|
|
496
|
-
color: var(--error);
|
|
497
|
-
padding: 6px 0;
|
|
498
|
-
font-size: 11px;
|
|
499
|
-
line-height: 1.4;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
.usage-error.hidden { display: none; }
|
|
503
|
-
|
|
504
|
-
.usage-bar-group {
|
|
505
|
-
margin-bottom: 10px;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
.usage-bar-group:last-child { margin-bottom: 0; }
|
|
509
|
-
.usage-bar-group.hidden { display: none; }
|
|
510
|
-
|
|
511
|
-
.usage-bar-label {
|
|
470
|
+
/* --- Plan usage link --- */
|
|
471
|
+
.usage-external-link {
|
|
512
472
|
display: flex;
|
|
513
|
-
justify-content: space-between;
|
|
514
473
|
align-items: center;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
.usage-bar-pct {
|
|
521
|
-
font-family: "SF Mono", Menlo, Monaco, monospace;
|
|
522
|
-
font-weight: 600;
|
|
523
|
-
font-size: 11px;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
.usage-bar-track {
|
|
527
|
-
height: 6px;
|
|
528
|
-
background: var(--input-bg);
|
|
474
|
+
justify-content: center;
|
|
475
|
+
gap: 6px;
|
|
476
|
+
padding: 8px 12px;
|
|
477
|
+
border-radius: 6px;
|
|
529
478
|
border: 1px solid var(--border-subtle);
|
|
530
|
-
|
|
531
|
-
|
|
479
|
+
background: var(--input-bg);
|
|
480
|
+
color: var(--text-secondary);
|
|
481
|
+
font-size: 12px;
|
|
482
|
+
text-decoration: none;
|
|
483
|
+
transition: background 0.15s, color 0.15s;
|
|
532
484
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
border-radius: 3px;
|
|
537
|
-
background: var(--accent);
|
|
538
|
-
transition: width 0.4s ease;
|
|
539
|
-
min-width: 0;
|
|
485
|
+
.usage-external-link:hover {
|
|
486
|
+
background: var(--hover-bg);
|
|
487
|
+
color: var(--text-primary);
|
|
540
488
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
.usage-bar-reset {
|
|
547
|
-
font-size: 10px;
|
|
548
|
-
color: var(--text-muted);
|
|
549
|
-
margin-top: 2px;
|
|
489
|
+
.usage-external-link svg {
|
|
490
|
+
width: 13px;
|
|
491
|
+
height: 13px;
|
|
492
|
+
flex-shrink: 0;
|
|
550
493
|
}
|
|
551
494
|
|
|
552
495
|
.usage-divider {
|
|
@@ -580,31 +523,6 @@
|
|
|
580
523
|
font-weight: 500;
|
|
581
524
|
}
|
|
582
525
|
|
|
583
|
-
/* --- Usage FAB --- */
|
|
584
|
-
#usage-fab {
|
|
585
|
-
position: fixed;
|
|
586
|
-
bottom: calc(var(--safe-bottom, 0px) + 16px);
|
|
587
|
-
right: 16px;
|
|
588
|
-
width: 40px;
|
|
589
|
-
height: 40px;
|
|
590
|
-
border-radius: 50%;
|
|
591
|
-
border: 1px solid var(--border);
|
|
592
|
-
background: var(--bg-alt);
|
|
593
|
-
color: var(--text-muted);
|
|
594
|
-
cursor: pointer;
|
|
595
|
-
display: flex;
|
|
596
|
-
align-items: center;
|
|
597
|
-
justify-content: center;
|
|
598
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
599
|
-
z-index: 90;
|
|
600
|
-
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
#usage-fab.hidden { display: none; }
|
|
604
|
-
#usage-fab .lucide { width: 18px; height: 18px; }
|
|
605
|
-
#usage-fab:hover { color: var(--text); border-color: var(--text-dimmer); }
|
|
606
|
-
#usage-fab.active { color: var(--accent); border-color: var(--accent); }
|
|
607
|
-
|
|
608
526
|
@media (max-width: 768px) {
|
|
609
527
|
#usage-panel {
|
|
610
528
|
top: auto;
|
|
@@ -614,8 +532,6 @@
|
|
|
614
532
|
width: auto;
|
|
615
533
|
max-height: 60vh;
|
|
616
534
|
}
|
|
617
|
-
#usage-fab { display: none !important; }
|
|
618
|
-
#usage-header-btn:not(.hidden) { display: flex; }
|
|
619
535
|
}
|
|
620
536
|
|
|
621
537
|
.status-dot.connected { background: var(--success); }
|
package/lib/public/index.html
CHANGED
|
@@ -80,9 +80,6 @@
|
|
|
80
80
|
<i data-lucide="ellipsis" style="width:14px;height:14px"></i>
|
|
81
81
|
</button>
|
|
82
82
|
<div id="sidebar-footer-menu" class="hidden">
|
|
83
|
-
<button class="sidebar-menu-item" id="footer-usage">
|
|
84
|
-
<i data-lucide="gauge"></i> <span>Usage</span>
|
|
85
|
-
</button>
|
|
86
83
|
<a class="sidebar-menu-item" href="https://github.com/chadbyte/claude-relay" target="_blank" rel="noopener">
|
|
87
84
|
<i data-lucide="github"></i> <span>GitHub</span>
|
|
88
85
|
</a>
|
|
@@ -125,7 +122,6 @@
|
|
|
125
122
|
</label>
|
|
126
123
|
</div>
|
|
127
124
|
</div>
|
|
128
|
-
<button id="usage-header-btn" class="hidden" title="Usage"><i data-lucide="dollar-sign"></i></button>
|
|
129
125
|
<button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
|
|
130
126
|
<button id="qr-btn" title="Share"><i data-lucide="share"></i></button>
|
|
131
127
|
<div id="qr-overlay" class="hidden">
|
|
@@ -173,33 +169,11 @@
|
|
|
173
169
|
<button id="usage-panel-close" aria-label="Close"><i data-lucide="x"></i></button>
|
|
174
170
|
</div>
|
|
175
171
|
<div class="usage-panel-body">
|
|
176
|
-
<
|
|
177
|
-
<
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<div class="usage-bar-label"><span>Current session</span><span class="usage-bar-pct" id="usage-pct-session"></span></div>
|
|
182
|
-
<div class="usage-bar-track"><div class="usage-bar-fill" id="usage-fill-session"></div></div>
|
|
183
|
-
<div class="usage-bar-reset" id="usage-reset-session"></div>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="usage-bar-group" id="usage-bar-weekly">
|
|
186
|
-
<div class="usage-bar-label"><span>Current week (all models)</span><span class="usage-bar-pct" id="usage-pct-weekly"></span></div>
|
|
187
|
-
<div class="usage-bar-track"><div class="usage-bar-fill" id="usage-fill-weekly"></div></div>
|
|
188
|
-
<div class="usage-bar-reset" id="usage-reset-weekly"></div>
|
|
189
|
-
</div>
|
|
190
|
-
<div class="usage-bar-group" id="usage-bar-sonnet">
|
|
191
|
-
<div class="usage-bar-label"><span>Current week (Sonnet only)</span><span class="usage-bar-pct" id="usage-pct-sonnet"></span></div>
|
|
192
|
-
<div class="usage-bar-track"><div class="usage-bar-fill" id="usage-fill-sonnet"></div></div>
|
|
193
|
-
<div class="usage-bar-reset" id="usage-reset-sonnet"></div>
|
|
194
|
-
</div>
|
|
195
|
-
<div class="usage-bar-group hidden" id="usage-bar-extra">
|
|
196
|
-
<div class="usage-bar-label"><span>Extra usage</span><span class="usage-bar-pct" id="usage-pct-extra"></span></div>
|
|
197
|
-
<div class="usage-bar-track"><div class="usage-bar-fill usage-bar-fill-extra" id="usage-fill-extra"></div></div>
|
|
198
|
-
<div class="usage-bar-reset" id="usage-reset-extra"></div>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
<div class="usage-divider" id="usage-session-divider"></div>
|
|
172
|
+
<a href="https://claude.ai/settings/usage" target="_blank" rel="noopener" class="usage-external-link">
|
|
173
|
+
<span>Check plan usage on claude.ai</span>
|
|
174
|
+
<i data-lucide="external-link"></i>
|
|
175
|
+
</a>
|
|
176
|
+
<div class="usage-divider"></div>
|
|
203
177
|
<div class="usage-section-label">Session</div>
|
|
204
178
|
<div class="usage-row"><span class="usage-label">Cost</span><span id="usage-cost" class="usage-value">$0.0000</span></div>
|
|
205
179
|
<div class="usage-row"><span class="usage-label">Input tokens</span><span id="usage-input" class="usage-value">0</span></div>
|
|
@@ -211,7 +185,6 @@
|
|
|
211
185
|
</div>
|
|
212
186
|
<div id="todo-sticky" class="hidden"></div>
|
|
213
187
|
<button id="new-msg-btn" class="hidden" aria-label="Scroll to bottom"></button>
|
|
214
|
-
<button id="usage-fab" class="hidden" aria-label="Usage"><i data-lucide="gauge"></i></button>
|
|
215
188
|
<div id="input-area">
|
|
216
189
|
<div id="input-wrapper">
|
|
217
190
|
<div id="slash-menu"></div>
|
|
@@ -137,7 +137,6 @@ export function initNotifications(_ctx) {
|
|
|
137
137
|
var footerBtn = $("sidebar-footer-btn");
|
|
138
138
|
var footerMenu = $("sidebar-footer-menu");
|
|
139
139
|
var footerUpdateCheck = $("footer-update-check");
|
|
140
|
-
var footerUsage = $("footer-usage");
|
|
141
140
|
if (!footerBtn || !footerMenu) return;
|
|
142
141
|
|
|
143
142
|
footerBtn.addEventListener("click", function (e) {
|
|
@@ -151,14 +150,6 @@ export function initNotifications(_ctx) {
|
|
|
151
150
|
}
|
|
152
151
|
});
|
|
153
152
|
|
|
154
|
-
if (footerUsage && ctx.toggleUsagePanel) {
|
|
155
|
-
footerUsage.addEventListener("click", function (e) {
|
|
156
|
-
e.stopPropagation();
|
|
157
|
-
footerMenu.classList.add("hidden");
|
|
158
|
-
ctx.toggleUsagePanel();
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
153
|
function setUpdateIcon(name, spin) {
|
|
163
154
|
var el = footerUpdateCheck.querySelector(".lucide, [data-lucide]");
|
|
164
155
|
if (!el) return;
|
package/package.json
CHANGED
package/lib/usage.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
var os = require("os");
|
|
2
|
-
var path = require("path");
|
|
3
|
-
var fs = require("fs");
|
|
4
|
-
var { execFileSync } = require("child_process");
|
|
5
|
-
var https = require("https");
|
|
6
|
-
|
|
7
|
-
var BASE_API_URL = "https://api.anthropic.com";
|
|
8
|
-
|
|
9
|
-
function readOAuthToken() {
|
|
10
|
-
// Priority 1: env var override
|
|
11
|
-
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
12
|
-
return process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Priority 2: macOS keychain
|
|
16
|
-
if (process.platform === "darwin") {
|
|
17
|
-
try {
|
|
18
|
-
var user = os.userInfo().username;
|
|
19
|
-
var data = execFileSync("security", [
|
|
20
|
-
"find-generic-password", "-a", user, "-w", "-s", "Claude Code-credentials"
|
|
21
|
-
], { encoding: "utf8", timeout: 5000 }).trim();
|
|
22
|
-
if (data) {
|
|
23
|
-
var parsed = JSON.parse(data);
|
|
24
|
-
if (parsed.claudeAiOauth && parsed.claudeAiOauth.accessToken) {
|
|
25
|
-
return parsed.claudeAiOauth.accessToken;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
} catch (e) { /* fall through */ }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Priority 3: plaintext credentials file
|
|
32
|
-
var configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
33
|
-
var credFile = path.join(configDir, ".credentials.json");
|
|
34
|
-
try {
|
|
35
|
-
var content = fs.readFileSync(credFile, "utf8");
|
|
36
|
-
var parsed = JSON.parse(content);
|
|
37
|
-
if (parsed.claudeAiOauth && parsed.claudeAiOauth.accessToken) {
|
|
38
|
-
return parsed.claudeAiOauth.accessToken;
|
|
39
|
-
}
|
|
40
|
-
} catch (e) { /* fall through */ }
|
|
41
|
-
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function fetchUsageData() {
|
|
46
|
-
return new Promise(function (resolve, reject) {
|
|
47
|
-
var token = readOAuthToken();
|
|
48
|
-
if (!token) {
|
|
49
|
-
reject(new Error("No OAuth token available"));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
var url = new URL(BASE_API_URL + "/api/oauth/usage");
|
|
54
|
-
var options = {
|
|
55
|
-
hostname: url.hostname,
|
|
56
|
-
port: url.port || 443,
|
|
57
|
-
path: url.pathname,
|
|
58
|
-
method: "GET",
|
|
59
|
-
headers: {
|
|
60
|
-
"Content-Type": "application/json",
|
|
61
|
-
"Authorization": "Bearer " + token,
|
|
62
|
-
"anthropic-beta": "oauth-2025-04-20",
|
|
63
|
-
"User-Agent": "claude-code/2.0.0",
|
|
64
|
-
},
|
|
65
|
-
timeout: 5000,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
var req = https.request(options, function (res) {
|
|
69
|
-
var body = "";
|
|
70
|
-
res.on("data", function (chunk) { body += chunk; });
|
|
71
|
-
res.on("end", function () {
|
|
72
|
-
if (res.statusCode !== 200) {
|
|
73
|
-
reject(new Error("Usage API returned " + res.statusCode));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
resolve(JSON.parse(body));
|
|
78
|
-
} catch (e) {
|
|
79
|
-
reject(new Error("Invalid JSON from usage API"));
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
req.on("error", function (err) { reject(err); });
|
|
85
|
-
req.on("timeout", function () { req.destroy(); reject(new Error("Usage API timeout")); });
|
|
86
|
-
req.end();
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
module.exports = { fetchUsageData: fetchUsageData };
|