@caius_kong/ccusage-dashboard 0.2.13 → 0.2.14
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 +25 -3
- package/lib/server.py +12 -16
- package/package.json +1 -1
package/lib/index.html
CHANGED
|
@@ -55,7 +55,9 @@
|
|
|
55
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}
|
|
56
56
|
.range button:hover{border-color:var(--accent)}
|
|
57
57
|
.range button.active{background:var(--accent);border-color:var(--accent);color:#0d1117;font-weight:600}
|
|
58
|
-
.sessid{font-variant-numeric:tabular-nums;letter-spacing:.3px}
|
|
58
|
+
.sessid{font-variant-numeric:tabular-nums;letter-spacing:.3px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}
|
|
59
|
+
.sessid:hover{background:var(--accent);border-color:var(--accent);color:#0d1117}
|
|
60
|
+
.sessid.copied{background:var(--green);border-color:var(--green);color:#0d1117}
|
|
59
61
|
.budget-band{margin:2px 0 14px;background:var(--panel-2);border-radius:8px;padding:12px 16px;display:flex;flex-direction:column;gap:8px}
|
|
60
62
|
.budget-band .bl{display:flex;justify-content:space-between;font-size:13px;color:var(--muted)}
|
|
61
63
|
.budget-band .bl b{color:var(--text)}
|
|
@@ -133,6 +135,24 @@ function fmtTok(n){
|
|
|
133
135
|
}
|
|
134
136
|
function fmtPct(n){return n==null?"–":n.toFixed(1)+"%";}
|
|
135
137
|
function esc(s){const d=document.createElement("div");d.textContent=s;return d.innerHTML;}
|
|
138
|
+
async function copySessionId(id, ev){
|
|
139
|
+
ev.stopPropagation();
|
|
140
|
+
const el=ev.currentTarget; // capture before any await; currentTarget is nulled after dispatch
|
|
141
|
+
try{
|
|
142
|
+
await navigator.clipboard.writeText(id);
|
|
143
|
+
}catch(e){
|
|
144
|
+
// fallback for non-secure contexts / older browsers
|
|
145
|
+
const ta=document.createElement("textarea");
|
|
146
|
+
ta.value=id;ta.style.position="fixed";ta.style.opacity="0";
|
|
147
|
+
document.body.appendChild(ta);ta.select();
|
|
148
|
+
try{document.execCommand("copy");}catch(_){}
|
|
149
|
+
document.body.removeChild(ta);
|
|
150
|
+
}
|
|
151
|
+
el.classList.add("copied");
|
|
152
|
+
const old=el.textContent;
|
|
153
|
+
el.textContent="✓ copied";
|
|
154
|
+
setTimeout(()=>{el.textContent=old;el.classList.remove("copied");},1200);
|
|
155
|
+
}
|
|
136
156
|
|
|
137
157
|
function renderModels(data){
|
|
138
158
|
const total=data.totalCost||0;
|
|
@@ -190,9 +210,11 @@ function renderSessions(data){
|
|
|
190
210
|
$("sessRange").textContent=`${total} sessions`;
|
|
191
211
|
if(!list.length){$("sessionsList").innerHTML='<div class="empty">no sessions in range</div>';return;}
|
|
192
212
|
$("sessionsList").innerHTML=list.map(s=>{
|
|
213
|
+
const shortId=s.id.slice(0,8);
|
|
214
|
+
const idPill=`<span class="pill sessid" title="click to copy full session id" onclick="copySessionId('${s.id.replace(/[\\']/g,"\\$&")}',event)">${esc(shortId)}</span>`;
|
|
193
215
|
const label = s.hasCwd
|
|
194
|
-
? `<span class="name" title="workdir: ${esc(s.cwd||s.projectKey)}">${esc(s.dirName||
|
|
195
|
-
: `<span class="name muted" title="no workdir found for this ${esc(s.agent)} session">${
|
|
216
|
+
? `<span class="name" title="workdir: ${esc(s.cwd||s.projectKey)}">${esc(s.dirName||shortId)} ${idPill}</span>`
|
|
217
|
+
: `<span class="name muted" title="no workdir found for this ${esc(s.agent)} session">${idPill}</span>`;
|
|
196
218
|
const last=(s.lastActivity||"").slice(0,10);
|
|
197
219
|
return `<div class="row">
|
|
198
220
|
<div class="row-top"><span>${label} <span class="pill">${esc(s.agent)}</span></span><span class="cost">${fmtUsd(s.cost)}</span></div>
|
package/lib/server.py
CHANGED
|
@@ -202,14 +202,13 @@ def sessions(since_days: int = 30, period: str = "", from_date: str = "", to_dat
|
|
|
202
202
|
Filtering: pass `period` in ("today" | "week" | "month") OR an explicit
|
|
203
203
|
date range via `from_date`/`to_date` (YYYY-MM-DD). `since_days` is kept for
|
|
204
204
|
backward compatibility with the old 7d/30d/90d/all selector.
|
|
205
|
-
"""
|
|
206
|
-
data = run_ccusage(
|
|
207
|
-
["session", "--by-agent", "--json", "--offline"],
|
|
208
|
-
TTL["/api/sessions"],
|
|
209
|
-
)
|
|
210
|
-
rows = data.get("session") or []
|
|
211
205
|
|
|
212
|
-
|
|
206
|
+
The window is passed to ccusage as --since/--until so it filters by usage
|
|
207
|
+
EVENT date (entry.date) before summarising, exactly like the CLI session
|
|
208
|
+
report. Filtering rows ourselves by lastActivity would keep a whole session's
|
|
209
|
+
lifetime cost even when only a few events fall in the window, inflating the
|
|
210
|
+
total (observed: today $229 vs ccusage's true $0.09).
|
|
211
|
+
"""
|
|
213
212
|
today = date.today()
|
|
214
213
|
lo = hi = None
|
|
215
214
|
if from_date and to_date:
|
|
@@ -232,20 +231,17 @@ def sessions(since_days: int = 30, period: str = "", from_date: str = "", to_dat
|
|
|
232
231
|
lo = today - timedelta(days=since_days)
|
|
233
232
|
hi = today
|
|
234
233
|
|
|
234
|
+
ccusage_args = ["session", "--json", "--offline"]
|
|
235
|
+
if lo and hi:
|
|
236
|
+
ccusage_args += ["--since", lo.isoformat(), "--until", hi.isoformat()]
|
|
237
|
+
data = run_ccusage(ccusage_args, TTL["/api/sessions"])
|
|
238
|
+
rows = data.get("session") or []
|
|
239
|
+
|
|
235
240
|
out = []
|
|
236
241
|
for r in rows:
|
|
237
242
|
meta = r.get("metadata") or {}
|
|
238
243
|
sid = r.get("period")
|
|
239
244
|
last_activity = meta.get("lastActivity") or ""
|
|
240
|
-
# date filter by last activity against [lo, hi]
|
|
241
|
-
if last_activity:
|
|
242
|
-
try:
|
|
243
|
-
act_date = date.fromisoformat(last_activity[:10])
|
|
244
|
-
except ValueError:
|
|
245
|
-
act_date = None
|
|
246
|
-
if act_date is not None:
|
|
247
|
-
if act_date < lo or act_date > hi:
|
|
248
|
-
continue
|
|
249
245
|
project_raw = meta.get("projectPath") or ""
|
|
250
246
|
agent_name = r.get("agent", "?")
|
|
251
247
|
cwd = ""
|
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.14",
|
|
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",
|