claude-usage-dashboard 1.2.0 → 1.2.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/package.json +1 -1
- package/public/js/api.js +1 -0
- package/public/js/app.js +15 -1
- package/public/js/components/plan-selector.js +21 -1
- package/server/credentials.js +15 -0
- package/server/routes/api.js +6 -0
package/package.json
CHANGED
package/public/js/api.js
CHANGED
|
@@ -13,3 +13,4 @@ export async function fetchCost(params = {}) { return (await fetch(`${BASE}/cost
|
|
|
13
13
|
export async function fetchCache(params = {}) { return (await fetch(`${BASE}/cache${qs(params)}`)).json(); }
|
|
14
14
|
export async function fetchStatus() { return (await fetch(`${BASE}/status`)).json(); }
|
|
15
15
|
export async function fetchQuota() { return (await fetch(`${BASE}/quota`)).json(); }
|
|
16
|
+
export async function fetchSubscription() { return (await fetch(`${BASE}/subscription`)).json(); }
|
package/public/js/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchUsage, fetchModels, fetchProjects, fetchSessions, fetchCost, fetchCache, fetchStatus, fetchQuota } from './api.js';
|
|
1
|
+
import { fetchUsage, fetchModels, fetchProjects, fetchSessions, fetchCost, fetchCache, fetchStatus, fetchQuota, fetchSubscription } from './api.js';
|
|
2
2
|
import { initDatePicker } from './components/date-picker.js';
|
|
3
3
|
import { initPlanSelector } from './components/plan-selector.js';
|
|
4
4
|
import { renderTokenTrend } from './charts/token-trend.js';
|
|
@@ -190,6 +190,20 @@ function init() {
|
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
// Auto-detect subscription tier
|
|
194
|
+
fetchSubscription().then(info => {
|
|
195
|
+
if (info.plan) {
|
|
196
|
+
planSelector.setDetectedPlan(info.plan);
|
|
197
|
+
state.plan = planSelector.getPlan();
|
|
198
|
+
}
|
|
199
|
+
const tierLabels = { pro: 'Pro', max5x: 'Max 5x', max20x: 'Max 20x' };
|
|
200
|
+
const label = tierLabels[info.plan];
|
|
201
|
+
if (label) {
|
|
202
|
+
const h2 = document.querySelector('#quota-section h2');
|
|
203
|
+
if (h2) h2.textContent = `Subscription Quota (${label})`;
|
|
204
|
+
}
|
|
205
|
+
}).catch(() => {});
|
|
206
|
+
|
|
193
207
|
loadAll();
|
|
194
208
|
loadQuota();
|
|
195
209
|
startAutoRefresh();
|
|
@@ -7,6 +7,7 @@ const PLANS = {
|
|
|
7
7
|
export function initPlanSelector(container, onChange) {
|
|
8
8
|
const saved = localStorage.getItem('selectedPlan') || 'max20x';
|
|
9
9
|
const savedPrice = localStorage.getItem('customPrice') || '';
|
|
10
|
+
let detectedPlan = null;
|
|
10
11
|
|
|
11
12
|
container.innerHTML = `
|
|
12
13
|
<select id="plan-select">
|
|
@@ -33,5 +34,24 @@ export function initPlanSelector(container, onChange) {
|
|
|
33
34
|
customInput.style.display = customInput.style.display === 'none' ? 'inline-block' : 'none';
|
|
34
35
|
});
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
function setDetectedPlan(planKey) {
|
|
38
|
+
if (!planKey || !PLANS[planKey]) return;
|
|
39
|
+
detectedPlan = planKey;
|
|
40
|
+
// Only auto-select if user hasn't manually chosen a plan
|
|
41
|
+
if (!localStorage.getItem('selectedPlan')) {
|
|
42
|
+
select.value = planKey;
|
|
43
|
+
emitChange();
|
|
44
|
+
}
|
|
45
|
+
// Update option labels to show which is detected
|
|
46
|
+
for (const opt of select.options) {
|
|
47
|
+
const p = PLANS[opt.value];
|
|
48
|
+
const suffix = opt.value === planKey ? ' ✓' : '';
|
|
49
|
+
opt.textContent = `${p.label} ($${p.price}/mo)${suffix}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
getPlan: () => ({ plan: select.value, customPrice: customInput.value ? parseFloat(customInput.value) : null }),
|
|
55
|
+
setDetectedPlan,
|
|
56
|
+
};
|
|
37
57
|
}
|
package/server/credentials.js
CHANGED
|
@@ -14,6 +14,21 @@ export function readCredentials(credentialsPath = CREDENTIALS_PATH) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export function getSubscriptionInfo(credentialsPath = CREDENTIALS_PATH) {
|
|
18
|
+
const creds = readCredentials(credentialsPath);
|
|
19
|
+
if (!creds) return null;
|
|
20
|
+
|
|
21
|
+
const { subscriptionType, rateLimitTier } = creds;
|
|
22
|
+
const combined = `${subscriptionType || ''} ${rateLimitTier || ''}`.toLowerCase();
|
|
23
|
+
|
|
24
|
+
let plan = null;
|
|
25
|
+
if (combined.includes('20x')) plan = 'max20x';
|
|
26
|
+
else if (combined.includes('5x')) plan = 'max5x';
|
|
27
|
+
else if (combined.includes('pro')) plan = 'pro';
|
|
28
|
+
|
|
29
|
+
return { subscriptionType: subscriptionType || null, rateLimitTier: rateLimitTier || null, plan };
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
export function getAccessToken(credentialsPath = CREDENTIALS_PATH) {
|
|
18
33
|
const creds = readCredentials(credentialsPath);
|
|
19
34
|
if (!creds || !creds.accessToken) return null;
|
package/server/routes/api.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseLogDirectory } from '../parser.js';
|
|
|
3
3
|
import { filterByDateRange, autoGranularity, aggregateByTime, aggregateBySession, aggregateByProject, aggregateByModel, aggregateCache } from '../aggregator.js';
|
|
4
4
|
import { calculateRecordCost, PLAN_DEFAULTS } from '../pricing.js';
|
|
5
5
|
import { createQuotaFetcher } from '../quota.js';
|
|
6
|
+
import { getSubscriptionInfo } from '../credentials.js';
|
|
6
7
|
|
|
7
8
|
export function createApiRouter(logBaseDir, options = {}) {
|
|
8
9
|
const router = Router();
|
|
@@ -109,6 +110,11 @@ export function createApiRouter(logBaseDir, options = {}) {
|
|
|
109
110
|
}
|
|
110
111
|
});
|
|
111
112
|
|
|
113
|
+
router.get('/subscription', (req, res) => {
|
|
114
|
+
const info = options.getSubscriptionInfo ? options.getSubscriptionInfo() : getSubscriptionInfo();
|
|
115
|
+
res.json(info || { plan: null, subscriptionType: null, rateLimitTier: null });
|
|
116
|
+
});
|
|
117
|
+
|
|
112
118
|
router.get('/status', (req, res) => {
|
|
113
119
|
res.json({
|
|
114
120
|
record_count: cachedRecords.length,
|