@caius_kong/ccusage-dashboard 0.2.4 → 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.
Files changed (2) hide show
  1. package/lib/server.py +56 -4
  2. package/package.json +1 -1
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,9 +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, # best-effort decoded path, empty if ccusage didn't provide it
234
- "dirName": dir_name, # last path segment; empty if ccusage didn't provide
235
- "projectKey": project_raw, # raw ccusage projectPath (authoritative, may be encoded)
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
236
247
  "hasCwd": bool(project_raw),
237
248
  }
238
249
  )
@@ -240,6 +251,47 @@ def sessions(since_days: int = 30) -> dict:
240
251
  return {"total": len(out), "sessions": out}
241
252
 
242
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
+
243
295
  def _decode_cwd(raw: str) -> str:
244
296
  """Best-effort decode of ccusage's projectPath into a readable path.
245
297
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caius_kong/ccusage-dashboard",
3
- "version": "0.2.4",
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",