@enigmax/dashboard 0.1.7 → 0.1.8

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/assets/index.html +71 -0
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -141,6 +141,15 @@
141
141
  .linput { flex: 1; min-width: 0; background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: 7px; padding: 5px 10px; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
142
142
  .laddb { background: var(--surface2); color: var(--accent); border: 1px solid var(--border); border-radius: 7px; padding: 5px 14px; font-size: 12px; cursor: pointer; font-family: inherit; }
143
143
  .laddb:hover { border-color: var(--accent); }
144
+ /* Claude-style usage window gauges (current session / weekly all / weekly Sonnet). */
145
+ .wgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 18px; }
146
+ .wcard { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
147
+ .wtitle { font-size: 13px; font-weight: 600; color: var(--text); }
148
+ .wreset { font-size: 12px; color: var(--muted); margin-top: 2px; }
149
+ .wval { font-size: 22px; font-weight: 600; color: var(--accent); margin-top: 12px; }
150
+ .wval.muted { font-size: 14px; font-weight: 500; color: var(--muted); }
151
+ .wset { display: flex; gap: 6px; margin-top: 10px; }
152
+ .wlimit { flex: 1; min-width: 0; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 7px; padding: 4px 8px; font-size: 12px; }
144
153
  .sys-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0 24px; }
145
154
  .sys-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--surface2); font-size: 13px; }
146
155
  .sys-item .k { color: var(--muted); }
@@ -276,6 +285,9 @@
276
285
  <p>Measured token consumption and estimated cost, read-only from your own Claude Code session transcripts (<code>~/.claude/projects</code>). Cost is an estimate from a per-model price table; real spend is billed by Anthropic. Nothing is sent anywhere.</p>
277
286
  </div>
278
287
  <div class="panel" id="usagePanel" style="display:none">
288
+ <h2 style="margin:0 0 4px">Usage limits <small>same windows Claude Code shows</small></h2>
289
+ <div class="sub" style="margin-bottom:12px">Reset times and tokens are measured from your transcripts; the <b>% used</b> needs your plan limit (set it on a card, or with <code>enigma config plan-weekly-limit &lt;tokens&gt;</code>). The weekly reset day/time is <code>enigma config plan-weekly-reset "mon 11:00"</code>.</div>
290
+ <div id="uWindows"></div>
279
291
  <div class="grid" style="margin-bottom:16px">
280
292
  <div class="card"><div class="label">Est. Cost</div><div id="uCost" class="value accent">-</div><div class="foot">All sessions, per-model pricing</div></div>
281
293
  <div class="card"><div class="label">Cache Saved</div><div id="uCacheMoney" class="value good">-</div><div class="foot">Est. from prompt caching</div></div>
@@ -515,6 +527,63 @@
515
527
  // Real tool-usage panel (Claude Code transcripts). null = the opt-in flag is off.
516
528
  function shortProject(p) { p = String(p || ""); return p.length > 44 ? "..." + p.slice(-41) : p; }
517
529
 
530
+ // --- Claude-style usage windows (current session / weekly all / weekly Sonnet) ---
531
+ function fmtResetIn(ms) {
532
+ const d = ms - Date.now();
533
+ if (d <= 0) return "now";
534
+ const h = Math.floor(d / 3600000), m = Math.round((d % 3600000) / 60000);
535
+ return h ? ("in " + h + " hr " + m + " min") : ("in " + m + " min");
536
+ }
537
+ function fmtResetAt(ms) {
538
+ const dt = new Date(ms);
539
+ return "Resets " + dt.toLocaleDateString([], { weekday: "short" }) + " " + dt.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
540
+ }
541
+ function windowCard(w, sub) {
542
+ const reset = w.kind === "session"
543
+ ? (w.resetsAt ? "Resets " + fmtResetIn(w.resetsAt) : "No active session")
544
+ : (w.resetsAt ? fmtResetAt(w.resetsAt) : "");
545
+ let body;
546
+ if (sub === "weeklySonnet" && (w.used || 0) === 0) {
547
+ body = '<div class="wval muted">You haven\'t used Sonnet yet</div>';
548
+ } else if (w.pct != null) {
549
+ const p = Math.round(w.pct);
550
+ const col = w.pct >= 90 ? "var(--accent2)" : "var(--accent)";
551
+ body = '<div class="wval">' + p + '% used</div>'
552
+ + '<div class="bar-track" style="margin-top:8px"><div class="bar-fill" style="width:' + Math.min(100, w.pct) + '%;background:' + col + '"></div></div>';
553
+ } else {
554
+ body = '<div class="wval">' + fmt(w.used || 0) + ' tok</div>'
555
+ + '<div class="wset"><input type="number" min="0" class="wlimit" data-plan="' + sub + '" placeholder="set limit (tokens)">'
556
+ + '<button type="button" class="laddb" data-plan="' + sub + '">Set</button></div>';
557
+ }
558
+ return '<div class="wcard"><div class="wtitle">' + esc(w.label) + '</div><div class="wreset">' + esc(reset) + '</div>' + body + '</div>';
559
+ }
560
+ function renderWindows(u) {
561
+ const w = u && u.windows;
562
+ if (!w) { $("uWindows").innerHTML = ""; return; }
563
+ $("uWindows").innerHTML = '<div class="wgrid">'
564
+ + windowCard(w.session, "session")
565
+ + windowCard(w.weeklyAll, "weekly")
566
+ + windowCard(w.weeklySonnet, "weeklySonnet")
567
+ + "</div>";
568
+ }
569
+ async function planSet(key, value) {
570
+ try { await fetch("/api/plan", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value: Number(value) }) }); refreshAll(true); }
571
+ catch { /* leave as-is */ }
572
+ }
573
+ function wireUsage() {
574
+ const el = $("uWindows");
575
+ el.addEventListener("click", (e) => {
576
+ const b = e.target.closest("button[data-plan]"); if (!b) return;
577
+ const inp = el.querySelector('input.wlimit[data-plan="' + b.dataset.plan + '"]');
578
+ if (inp && inp.value) planSet(b.dataset.plan, inp.value);
579
+ });
580
+ el.addEventListener("keydown", (e) => {
581
+ if (e.key !== "Enter") return;
582
+ const inp = e.target.closest("input.wlimit");
583
+ if (inp && inp.value) planSet(inp.dataset.plan, inp.value);
584
+ });
585
+ }
586
+
518
587
  // One token/cost breakdown table (By Model / By Project), sorted by cost then tokens.
519
588
  function renderUsageTable(el, map, pending, label) {
520
589
  const entries = Object.entries(map || {}).sort((a, b) => ((b[1].cost || 0) - (a[1].cost || 0)) || ((b[1].input + b[1].output) - (a[1].input + a[1].output)));
@@ -540,6 +609,7 @@
540
609
  if (!u) { panel.style.display = "none"; hint.style.display = "block"; return; }
541
610
  hint.style.display = "none";
542
611
  panel.style.display = "block";
612
+ renderWindows(u);
543
613
  $("uCost").textContent = fmtUsd(u.cost || 0);
544
614
  $("uCacheMoney").textContent = fmtUsd(cacheMoney(u.cacheRead || 0));
545
615
  $("uCacheRead").textContent = fmt(u.cacheRead || 0);
@@ -1180,6 +1250,7 @@
1180
1250
  wireSettings();
1181
1251
  wireSkills();
1182
1252
  wireAccounts();
1253
+ wireUsage();
1183
1254
  $("cfgExport").addEventListener("click", exportConfig);
1184
1255
  $("cfgImport").addEventListener("click", importConfig);
1185
1256
  $("updateBtn").addEventListener("click", runUpdate);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
5
5
  "type": "module",
6
6
  "files": [