@caius_kong/ccusage-dashboard 0.2.2 → 0.2.4
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/index.html +22 -17
- package/lib/server.py +10 -3
- package/package.json +1 -1
package/lib/index.html
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
.tab{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:7px 16px;cursor:pointer;color:var(--muted);font-size:13px}
|
|
24
24
|
.tab.active{background:var(--panel-2);border-color:var(--accent);color:var(--text);font-weight:600}
|
|
25
25
|
.tab:hover{color:var(--text)}
|
|
26
|
+
.tab:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
26
27
|
.tab-budget{margin-left:auto;display:flex;align-items:center;gap:10px}
|
|
27
28
|
.budget-pill{border-radius:999px;padding:4px 12px;font-size:12px;font-weight:600;border:1px solid var(--border)}
|
|
28
29
|
.budget-ok{color:var(--green);border-color:var(--border)}
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
.err{color:var(--red)}
|
|
50
51
|
.pill{background:var(--panel-2);border:1px solid var(--border);border-radius:999px;padding:1px 8px;font-size:11px;color:var(--muted);margin-left:6px}
|
|
51
52
|
.hint{color:var(--muted);font-size:12px;margin-top:14px;text-align:center}
|
|
52
|
-
.range{display:flex;gap:8px;align-items:center;margin-left:6px}
|
|
53
|
+
.range{display:flex;gap:8px;align-items:center;margin-left:6px;flex-wrap:wrap}
|
|
53
54
|
.range input{background:var(--panel-2);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:5px 8px;font-size:12px;font-family:inherit}
|
|
54
55
|
.range button{background:var(--panel-2);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:5px 12px;cursor:pointer;font-size:12px}
|
|
55
56
|
.range button:hover{border-color:var(--accent)}
|
|
@@ -71,12 +72,12 @@
|
|
|
71
72
|
<span class="status"><span class="dot" id="dot"></span><span id="status">connecting…</span></span>
|
|
72
73
|
</header>
|
|
73
74
|
|
|
74
|
-
<div class="tabs">
|
|
75
|
-
<div class="tab active" data-tab="today">Today</div>
|
|
76
|
-
<div class="tab" data-tab="week">This Week</div>
|
|
77
|
-
<div class="tab" data-tab="month">This Month</div>
|
|
78
|
-
<div class="tab" data-tab="range">Custom Range</div>
|
|
79
|
-
<div class="tab" data-tab="sessions">Sessions</div>
|
|
75
|
+
<div class="tabs" role="tablist" aria-label="Time period">
|
|
76
|
+
<div class="tab active" role="tab" tabindex="0" aria-selected="true" data-tab="today">Today</div>
|
|
77
|
+
<div class="tab" role="tab" tabindex="0" aria-selected="false" data-tab="week">This Week</div>
|
|
78
|
+
<div class="tab" role="tab" tabindex="0" aria-selected="false" data-tab="month">This Month</div>
|
|
79
|
+
<div class="tab" role="tab" tabindex="0" aria-selected="false" data-tab="range">Custom Range</div>
|
|
80
|
+
<div class="tab" role="tab" tabindex="0" aria-selected="false" data-tab="sessions">Sessions</div>
|
|
80
81
|
<div class="tab-budget">
|
|
81
82
|
<span class="budget-pill" id="budgetPill">budget …</span>
|
|
82
83
|
</div>
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
<div id="sessionsList"><div class="empty">loading…</div></div>
|
|
123
124
|
</div>
|
|
124
125
|
|
|
125
|
-
<div class="hint">auto-refresh
|
|
126
|
+
<div class="hint">auto-refresh 15s · costs in USD · <span id="lastUpd"></span></div>
|
|
126
127
|
|
|
127
128
|
<script>
|
|
128
129
|
const $=(id)=>document.getElementById(id);
|
|
@@ -132,6 +133,7 @@ let active="today", modelCtx=[], sessDays=30;
|
|
|
132
133
|
function fmtUsd(n){return "$"+(n==null?"–":Number(n).toFixed(2));}
|
|
133
134
|
function fmtTok(n){
|
|
134
135
|
if(n==null)return "–";
|
|
136
|
+
if(n>=1e9)return (n/1e9).toFixed(2)+"B";
|
|
135
137
|
if(n>=1e6)return (n/1e6).toFixed(2)+"M";
|
|
136
138
|
if(n>=1e3)return (n/1e3).toFixed(1)+"k";
|
|
137
139
|
return String(n);
|
|
@@ -164,10 +166,10 @@ function applyBudget(month){
|
|
|
164
166
|
fill.style.width=Math.min(pct,100)+"%";
|
|
165
167
|
fill.className = pct>=100?"bg-danger":(pct>=80?"bg-warn":"bg-ok");
|
|
166
168
|
$("budgetBand").style.display="flex";
|
|
167
|
-
let cls="budget-ok",txt=`budget ${fmtUsd(cap)} · ${pct.toFixed(0)}
|
|
168
|
-
if(pct>=100){cls="budget-danger";txt=`⚠ ${fmtUsd(spent)} ≥ budget`;}
|
|
169
|
-
else if(pct>=80){cls="budget-warn";txt=`${fmtUsd(spent)} / ${fmtUsd(cap)} (${pct.toFixed(0)}%)`;}
|
|
170
|
-
const p=$("budgetPill");p.className="budget-pill "+cls;p.textContent=txt;
|
|
169
|
+
let cls="budget-ok",txt=`budget ${fmtUsd(cap)} · ${pct.toFixed(0)}%`,tip=`${fmtUsd(spent)} of ${fmtUsd(cap)} (${pct.toFixed(1)}%)`;
|
|
170
|
+
if(pct>=100){cls="budget-danger";txt=`⚠ ${fmtUsd(spent)} ≥ budget`;tip=`${fmtUsd(spent)} spent · ${pct.toFixed(1)}% of ${fmtUsd(cap)} budget`;}
|
|
171
|
+
else if(pct>=80){cls="budget-warn";txt=`${fmtUsd(spent)} / ${fmtUsd(cap)} (${pct.toFixed(0)}%)`;tip=`${fmtUsd(spent)} of ${fmtUsd(cap)} (${pct.toFixed(1)}%)`;}
|
|
172
|
+
const p=$("budgetPill");p.className="budget-pill "+cls;p.textContent=txt;p.title=tip;
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
async function loadSessions(){
|
|
@@ -186,8 +188,8 @@ function renderSessions(data){
|
|
|
186
188
|
if(!list.length){$("sessionsList").innerHTML='<div class="empty">no sessions in range</div>';return;}
|
|
187
189
|
$("sessionsList").innerHTML=list.map(s=>{
|
|
188
190
|
const label = s.hasCwd
|
|
189
|
-
? `<span class="name">${esc(s.dirName||s.
|
|
190
|
-
: `<span class="name muted">${esc(s.id.slice(0,8))}</span>`;
|
|
191
|
+
? `<span class="name" title="workdir from ccusage: ${esc(s.cwd||s.projectKey)}">${esc(s.dirName||s.id.slice(0,8))} <span class="pill sessid">${esc(s.id.slice(0,8))}</span></span>`
|
|
192
|
+
: `<span class="name muted" title="workdir not provided by ccusage for ${esc(s.agent)}">${esc(s.id.slice(0,8))}</span>`;
|
|
191
193
|
const last=(s.lastActivity||"").slice(0,10);
|
|
192
194
|
return `<div class="row">
|
|
193
195
|
<div class="row-top"><span>${label} <span class="pill">${esc(s.agent)}</span></span><span class="cost">${fmtUsd(s.cost)}</span></div>
|
|
@@ -302,9 +304,10 @@ async function tick(){
|
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
document.querySelectorAll(".tab").forEach(t=>{
|
|
305
|
-
|
|
306
|
-
document.querySelectorAll(".tab").forEach(x=>x.classList.remove("active"));
|
|
307
|
+
const activate=()=>{
|
|
308
|
+
document.querySelectorAll(".tab").forEach(x=>{x.classList.remove("active");x.setAttribute("aria-selected","false");});
|
|
307
309
|
t.classList.add("active");
|
|
310
|
+
t.setAttribute("aria-selected","true");
|
|
308
311
|
active=t.dataset.tab;
|
|
309
312
|
$("rangeRow").style.display = active==="range"?"block":"none";
|
|
310
313
|
$("sessionsPanel").style.display = active==="sessions"?"block":"none";
|
|
@@ -316,7 +319,9 @@ document.querySelectorAll(".tab").forEach(t=>{
|
|
|
316
319
|
}else{
|
|
317
320
|
loadModels();
|
|
318
321
|
}
|
|
319
|
-
}
|
|
322
|
+
};
|
|
323
|
+
t.addEventListener("click",activate);
|
|
324
|
+
t.addEventListener("keydown",(e)=>{if(e.key==="Enter"||e.key===" "||e.key==="Spacebar"){e.preventDefault();activate();}});
|
|
320
325
|
});
|
|
321
326
|
$("rangeGo").addEventListener("click",rangeApply);
|
|
322
327
|
document.querySelectorAll(".sday").forEach(b=>{
|
package/lib/server.py
CHANGED
|
@@ -230,8 +230,9 @@ def sessions(since_days: int = 30) -> dict:
|
|
|
230
230
|
"cacheReadTokens": r.get("cacheReadTokens", 0),
|
|
231
231
|
"cacheCreationTokens": r.get("cacheCreationTokens", 0),
|
|
232
232
|
"lastActivity": last_activity,
|
|
233
|
-
"cwd": cwd,
|
|
233
|
+
"cwd": cwd, # best-effort decoded path, empty if ccusage didn't provide it
|
|
234
234
|
"dirName": dir_name, # last path segment; empty if ccusage didn't provide
|
|
235
|
+
"projectKey": project_raw, # raw ccusage projectPath (authoritative, may be encoded)
|
|
235
236
|
"hasCwd": bool(project_raw),
|
|
236
237
|
}
|
|
237
238
|
)
|
|
@@ -242,12 +243,18 @@ def sessions(since_days: int = 30) -> dict:
|
|
|
242
243
|
def _decode_cwd(raw: str) -> str:
|
|
243
244
|
"""Best-effort decode of ccusage's projectPath into a readable path.
|
|
244
245
|
|
|
245
|
-
pi's projectPath is a Claude-Code-style encoded dir name where '/' became '-'
|
|
246
|
+
pi's projectPath is a Claude-Code-style encoded dir name where '/' became '-',
|
|
247
|
+
and literal '-' inside a dir name is NOT distinguishable from the separator
|
|
248
|
+
(encoding is lossy). So this is approximate: 'fund-tracker' may come back as
|
|
249
|
+
'fund/tracker'. The authoritative string is the raw projectPath itself.
|
|
246
250
|
Example: '--Users-caius-kong-Documents-...-AutoTrans--' -> /Users/caius_kong/.../AutoTrans
|
|
247
251
|
"""
|
|
248
252
|
if not raw:
|
|
249
253
|
return ""
|
|
250
|
-
|
|
254
|
+
parts = [p for p in raw.replace("-", "/").split("/") if p]
|
|
255
|
+
if not parts:
|
|
256
|
+
return ""
|
|
257
|
+
return "/" + "/".join(parts)
|
|
251
258
|
|
|
252
259
|
|
|
253
260
|
def _basename(raw: str) -> str:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caius_kong/ccusage-dashboard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "One-command local dashboard for ccusage: today/week/month/custom cost by model, 30-day trend, monthly budget alert. Numbers straight from ccusage.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|