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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-usage-dashboard",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Dashboard that visualizes Claude Code usage from local session logs",
5
5
  "main": "server/index.js",
6
6
  "bin": {
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
- return { getPlan: () => ({ plan: select.value, customPrice: customInput.value ? parseFloat(customInput.value) : null }) };
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
  }
@@ -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;
@@ -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,