@caius_kong/ccusage-dashboard 0.2.3 → 0.2.5
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 +2 -2
- package/lib/server.py +64 -5
- package/package.json +1 -1
package/lib/index.html
CHANGED
|
@@ -188,8 +188,8 @@ function renderSessions(data){
|
|
|
188
188
|
if(!list.length){$("sessionsList").innerHTML='<div class="empty">no sessions in range</div>';return;}
|
|
189
189
|
$("sessionsList").innerHTML=list.map(s=>{
|
|
190
190
|
const label = s.hasCwd
|
|
191
|
-
? `<span class="name">${esc(s.dirName||s.
|
|
192
|
-
: `<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>`;
|
|
193
193
|
const last=(s.lastActivity||"").slice(0,10);
|
|
194
194
|
return `<div class="row">
|
|
195
195
|
<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
|
@@ -218,8 +218,19 @@ def sessions(since_days: int = 30) -> dict:
|
|
|
218
218
|
if act_date is not None and act_date < cutoff:
|
|
219
219
|
continue
|
|
220
220
|
project_raw = meta.get("projectPath") or ""
|
|
221
|
-
cwd = _decode_cwd(project_raw)
|
|
222
221
|
dir_name = _basename(project_raw)
|
|
222
|
+
cwd = _decode_cwd(project_raw)
|
|
223
|
+
# For pi sessions the authoritative workdir lives on disk (session file first-line
|
|
224
|
+
# "cwd"). Your dashboards want the REAL dir name (e.g. "fund-tracker", not the
|
|
225
|
+
# lossy basename "tracker"). This only affects the DISPLAY label — all numeric
|
|
226
|
+
# cost/token data still comes 100% from ccusage.
|
|
227
|
+
if project_raw and dir_name:
|
|
228
|
+
disk_cwd = _pi_cwd_from_disk(project_raw)
|
|
229
|
+
if disk_cwd:
|
|
230
|
+
cwd = disk_cwd
|
|
231
|
+
disk_base = disk_cwd.rstrip("/").split("/")[-1]
|
|
232
|
+
if disk_base:
|
|
233
|
+
dir_name = disk_base
|
|
223
234
|
out.append(
|
|
224
235
|
{
|
|
225
236
|
"id": sid or "",
|
|
@@ -230,8 +241,9 @@ def sessions(since_days: int = 30) -> dict:
|
|
|
230
241
|
"cacheReadTokens": r.get("cacheReadTokens", 0),
|
|
231
242
|
"cacheCreationTokens": r.get("cacheCreationTokens", 0),
|
|
232
243
|
"lastActivity": last_activity,
|
|
233
|
-
"cwd": cwd,
|
|
234
|
-
"dirName": dir_name, #
|
|
244
|
+
"cwd": cwd, # real workdir when known (pi), else best-effort decode
|
|
245
|
+
"dirName": dir_name, # real dir name when known (pi), else last path segment
|
|
246
|
+
"projectKey": project_raw, # raw ccusage projectPath
|
|
235
247
|
"hasCwd": bool(project_raw),
|
|
236
248
|
}
|
|
237
249
|
)
|
|
@@ -239,15 +251,62 @@ def sessions(since_days: int = 30) -> dict:
|
|
|
239
251
|
return {"total": len(out), "sessions": out}
|
|
240
252
|
|
|
241
253
|
|
|
254
|
+
def _pi_cwd_from_disk(project_key: str):
|
|
255
|
+
"""Best-effort real workdir for a pi session project.
|
|
256
|
+
|
|
257
|
+
pi stores sessions under ~/.pi/agent/sessions/<projectKey>/<ts>_<id>.jsonl,
|
|
258
|
+
whose first line carries an authoritative "cwd". Reading it lets the dashboard
|
|
259
|
+
show the REAL dir name (e.g. "fund-tracker") instead of the lossy basename that
|
|
260
|
+
the encoded projectPath produces ("tracker").
|
|
261
|
+
|
|
262
|
+
This only supplies the DISPLAY label — costs/tokens still come from ccusage.
|
|
263
|
+
Returns None (caller falls back) if the dir/file is missing or unreadable.
|
|
264
|
+
"""
|
|
265
|
+
import glob
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
root = Path.home() / ".pi" / "agent" / "sessions" / project_key
|
|
269
|
+
if not root.is_dir():
|
|
270
|
+
return None
|
|
271
|
+
candidates = sorted(glob.glob(str(root / "*.jsonl")))
|
|
272
|
+
if not candidates:
|
|
273
|
+
return None
|
|
274
|
+
for path in candidates:
|
|
275
|
+
try:
|
|
276
|
+
with open(path, encoding="utf-8") as fh:
|
|
277
|
+
for line in fh:
|
|
278
|
+
line = line.strip()
|
|
279
|
+
if not line:
|
|
280
|
+
continue
|
|
281
|
+
try:
|
|
282
|
+
rec = json.loads(line)
|
|
283
|
+
except Exception:
|
|
284
|
+
continue
|
|
285
|
+
cwd = rec.get("cwd")
|
|
286
|
+
if isinstance(cwd, str) and cwd.strip():
|
|
287
|
+
return cwd.strip().rstrip("/")
|
|
288
|
+
except (OSError, ValueError):
|
|
289
|
+
continue
|
|
290
|
+
except Exception:
|
|
291
|
+
return None
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
|
|
242
295
|
def _decode_cwd(raw: str) -> str:
|
|
243
296
|
"""Best-effort decode of ccusage's projectPath into a readable path.
|
|
244
297
|
|
|
245
|
-
pi's projectPath is a Claude-Code-style encoded dir name where '/' became '-'
|
|
298
|
+
pi's projectPath is a Claude-Code-style encoded dir name where '/' became '-',
|
|
299
|
+
and literal '-' inside a dir name is NOT distinguishable from the separator
|
|
300
|
+
(encoding is lossy). So this is approximate: 'fund-tracker' may come back as
|
|
301
|
+
'fund/tracker'. The authoritative string is the raw projectPath itself.
|
|
246
302
|
Example: '--Users-caius-kong-Documents-...-AutoTrans--' -> /Users/caius_kong/.../AutoTrans
|
|
247
303
|
"""
|
|
248
304
|
if not raw:
|
|
249
305
|
return ""
|
|
250
|
-
|
|
306
|
+
parts = [p for p in raw.replace("-", "/").split("/") if p]
|
|
307
|
+
if not parts:
|
|
308
|
+
return ""
|
|
309
|
+
return "/" + "/".join(parts)
|
|
251
310
|
|
|
252
311
|
|
|
253
312
|
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.5",
|
|
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",
|