@enigmax/dashboard 0.1.23 → 0.1.26
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/assets/index.html +81 -8
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
.set-ctl { flex-shrink: 0; }
|
|
159
159
|
.toggle {
|
|
160
160
|
background: var(--surface2); color: var(--muted); border: 1px solid var(--border);
|
|
161
|
-
border-radius: var(--radius-sm); padding: 6px 16px; font-size: 12px; cursor: pointer; font-family: var(--mono); min-width: 56px;
|
|
161
|
+
border-radius: var(--radius-sm); padding: 6px 16px; font-size: 12px; cursor: pointer; font-family: var(--mono); min-width: 56px; text-decoration: none;
|
|
162
162
|
transition: color .15s, background .15s, border-color .15s;
|
|
163
163
|
}
|
|
164
164
|
.toggle:hover { color: var(--text); border-color: #2f3744; }
|
|
@@ -215,6 +215,11 @@
|
|
|
215
215
|
.btn-danger.icon { display: inline-flex; align-items: center; justify-content: center; padding: 6px 9px; }
|
|
216
216
|
.btn-danger.icon:hover { color: var(--bad); border-color: var(--bad); }
|
|
217
217
|
.btn-danger svg { width: 15px; height: 15px; display: block; }
|
|
218
|
+
/* Per-agent skill toggles: one pill per app (Claude Code, Codex, OpenCode), green when the skill is on for it. */
|
|
219
|
+
.set-agents { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
|
220
|
+
.achip { font-family: var(--mono); font-size: 11px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface2); color: var(--muted); cursor: pointer; text-decoration: none; transition: color .15s, background .15s, border-color .15s; }
|
|
221
|
+
.achip:hover { border-color: #2f3744; color: var(--text); }
|
|
222
|
+
.achip.on { background: var(--good); color: #06210f; border-color: var(--good); font-weight: 600; }
|
|
218
223
|
/* List settings (guard block/allow globs, custom secret patterns): chips + add input. A list row spans the full grid width. */
|
|
219
224
|
.set-row.list { grid-column: 1 / -1; align-items: stretch; flex-direction: column; }
|
|
220
225
|
.lchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
@@ -354,7 +359,7 @@
|
|
|
354
359
|
<div class="panel-head">
|
|
355
360
|
<h2>Enigma Systems <small>what's active - configuration, not savings</small></h2>
|
|
356
361
|
<div class="ctrls">
|
|
357
|
-
<button id="refreshBtn" type="button" class="
|
|
362
|
+
<button id="refreshBtn" type="button" class="iconbtn" title="Refresh" aria-label="Refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg></button>
|
|
358
363
|
<button id="fixPathBtn" type="button" class="toggle">Fix tool paths</button>
|
|
359
364
|
<button id="updateBtn" type="button" class="toggle on">Check & update</button>
|
|
360
365
|
</div>
|
|
@@ -444,7 +449,7 @@
|
|
|
444
449
|
</div>
|
|
445
450
|
<div class="panel" id="usagePanel" style="display:none">
|
|
446
451
|
<h2 style="margin:0 0 4px">Usage limits <small>same windows Claude Code shows</small></h2>
|
|
447
|
-
<div class="sub" style="margin-bottom:12px">Cards marked <span class="tag meas">live</span> show the <b>real % and reset</b> Anthropic reports (the same as Claude's own UI)
|
|
452
|
+
<div class="sub" style="margin-bottom:12px">Cards marked <span class="tag meas">live</span> show the <b>real % and reset</b> Anthropic reports (the same as Claude's own UI). Turn on <code>enigma config usage-api on</code> to fetch them with your local Claude Code login - no proxy or plan limit needed. (The proxy also captures them from <code>enigma claude</code> traffic.) Until live data lands, the card falls back to tokens used, or a % against a plan limit you set (on the card, or <code>enigma config plan-weekly-limit <tokens></code>). Per-model live data appears only when Anthropic sends it.</div>
|
|
448
453
|
<div id="uWindows"></div>
|
|
449
454
|
<div class="grid" style="margin-bottom:16px">
|
|
450
455
|
<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>
|
|
@@ -1677,6 +1682,7 @@ export {
|
|
|
1677
1682
|
$("uWindows").innerHTML = '<div class="wgrid">'
|
|
1678
1683
|
+ windowCard(w.session, "session")
|
|
1679
1684
|
+ windowCard(w.weeklyAll, "weekly")
|
|
1685
|
+
+ (w.weeklyOpus ? windowCard(w.weeklyOpus, "weeklyOpus") : "")
|
|
1680
1686
|
+ windowCard(w.weeklySonnet, "weeklySonnet")
|
|
1681
1687
|
+ "</div>";
|
|
1682
1688
|
}
|
|
@@ -1766,7 +1772,8 @@ export {
|
|
|
1766
1772
|
$("uBlockBar").style.width = pct + "%";
|
|
1767
1773
|
$("uBlockState").textContent = b.active ? "active" : "window elapsed";
|
|
1768
1774
|
const remMin = Math.max(0, Math.round((b.endsAt - now) / 60000));
|
|
1769
|
-
|
|
1775
|
+
const remLabel = remMin >= 60 ? Math.floor(remMin / 60) + "h " + (remMin % 60) + "m left" : remMin + "m left";
|
|
1776
|
+
$("uBlockTime").textContent = b.active ? remLabel : "ended";
|
|
1770
1777
|
$("uBlockTokens").textContent = fmt(b.tokens || 0);
|
|
1771
1778
|
$("uBlockCost").textContent = fmtUsd(b.cost || 0);
|
|
1772
1779
|
$("uBlockBurn").textContent = fmt(Math.round(b.burnRatePerMin || 0)) + " tok/min";
|
|
@@ -1911,11 +1918,19 @@ export {
|
|
|
1911
1918
|
// update), so the served UI may be newer than this tab - reload once so a tab you
|
|
1912
1919
|
// left open self-heals instead of showing stale markup.
|
|
1913
1920
|
let loadedBoot = null;
|
|
1921
|
+
// The UI bundle version this tab loaded against. A change means the served
|
|
1922
|
+
// @enigmax/dashboard bundle was swapped underneath us (a background update found a
|
|
1923
|
+
// newer version) - reload once so the tab picks up the new markup/scripts.
|
|
1924
|
+
let loadedUi = null;
|
|
1914
1925
|
function render(data) {
|
|
1915
1926
|
if (data.boot != null) {
|
|
1916
1927
|
if (loadedBoot === null) loadedBoot = data.boot;
|
|
1917
1928
|
else if (data.boot !== loadedBoot) { location.reload(); return; }
|
|
1918
1929
|
}
|
|
1930
|
+
if (data.ui != null) {
|
|
1931
|
+
if (loadedUi === null) loadedUi = data.ui;
|
|
1932
|
+
else if (data.ui !== loadedUi) { location.reload(); return; }
|
|
1933
|
+
}
|
|
1919
1934
|
priceOverride = Number(data.priceOverride) || 0;
|
|
1920
1935
|
speedOverride = Number(data.speedOverride) || 0;
|
|
1921
1936
|
const s = data.stats || {};
|
|
@@ -1974,6 +1989,50 @@ export {
|
|
|
1974
1989
|
}
|
|
1975
1990
|
}
|
|
1976
1991
|
|
|
1992
|
+
// --- dashboard self-update -------------------------------------------------
|
|
1993
|
+
// Where the latest published dashboard version lives: the raw package.json on the
|
|
1994
|
+
// repo's main branch (no API, no auth, no rate limit). A repo rename 404s here and is
|
|
1995
|
+
// handled like any other failure - the check simply no-ops until the URL resolves again.
|
|
1996
|
+
const DASH_PKG_URL = "https://raw.githubusercontent.com/FJRG2007/enigma/main/packages/dashboard/package.json";
|
|
1997
|
+
const UPDATE_CHECK_MS = 15 * 60 * 1000; // every 15 min; the dashboard rarely ships
|
|
1998
|
+
let updating = false; // one in-flight update at a time; reload happens via the ui-version change
|
|
1999
|
+
|
|
2000
|
+
// Numeric semver-ish compare: true when `a` is strictly newer than `b` (e.g. "0.2.0" > "0.1.9").
|
|
2001
|
+
function isNewer(a, b) {
|
|
2002
|
+
const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
|
|
2003
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
2004
|
+
const x = pa[i] || 0, y = pb[i] || 0;
|
|
2005
|
+
if (x !== y) return x > y;
|
|
2006
|
+
}
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// Ask GitHub whether a newer dashboard bundle was published; if so, tell the local
|
|
2011
|
+
// server to fetch it (the ui-version change in /api/stats then reloads the page).
|
|
2012
|
+
// Fully fault-tolerant: an 8s timeout plus try/catch swallow offline/timeout/parse/CORS
|
|
2013
|
+
// failures, and it simply retries on the next interval. Skipped while the tab is hidden.
|
|
2014
|
+
async function checkDashboardUpdate() {
|
|
2015
|
+
if (document.hidden || updating || loadedUi == null) return;
|
|
2016
|
+
let latest;
|
|
2017
|
+
try {
|
|
2018
|
+
const ctrl = new AbortController();
|
|
2019
|
+
const t = setTimeout(() => ctrl.abort(), 8000);
|
|
2020
|
+
let res;
|
|
2021
|
+
try { res = await fetch(DASH_PKG_URL, { cache: "no-store", signal: ctrl.signal }); }
|
|
2022
|
+
finally { clearTimeout(t); }
|
|
2023
|
+
if (!res.ok) return;
|
|
2024
|
+
latest = (await res.json()).version;
|
|
2025
|
+
} catch { return; } // offline, timeout, blocked, or bad JSON: try again next interval
|
|
2026
|
+
if (!latest || !isNewer(latest, loadedUi)) return;
|
|
2027
|
+
// A newer bundle exists: have the local server fetch it. The reload is driven by the
|
|
2028
|
+
// ui-version change in the stats poll, so an npm lag (changed:false) just retries later.
|
|
2029
|
+
updating = true;
|
|
2030
|
+
try {
|
|
2031
|
+
await fetch("/api/update", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" });
|
|
2032
|
+
} catch { /* server unreachable: retry next interval */ }
|
|
2033
|
+
finally { updating = false; }
|
|
2034
|
+
}
|
|
2035
|
+
|
|
1977
2036
|
// --- settings subpage (mirrors the TUI registry over /api/settings) ---
|
|
1978
2037
|
function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); }
|
|
1979
2038
|
|
|
@@ -2168,6 +2227,7 @@ export {
|
|
|
2168
2227
|
item("Context compression (MCP) · measured", boolPill(s.compress))
|
|
2169
2228
|
+ item("Real tool-usage stats · measured", boolPill(s.usageStats))
|
|
2170
2229
|
+ item("Claude Code proxy · experimental", boolPill(s.proxy))
|
|
2230
|
+
+ item("Live usage windows · real", boolPill(s.usageApi))
|
|
2171
2231
|
+ proxyMeasured
|
|
2172
2232
|
+ proxyLast
|
|
2173
2233
|
+ promptGuard
|
|
@@ -2284,8 +2344,18 @@ export {
|
|
|
2284
2344
|
+ (s.discarded ? "Disabled - click to enable" : "Enabled - click to disable") + '" data-name="' + esc(s.name)
|
|
2285
2345
|
+ '" data-action="' + (s.discarded ? "enable" : "disable") + '">' + (s.discarded ? "Disabled" : "Enabled") + "</button>"
|
|
2286
2346
|
: '<button type="button" class="btn-danger" data-name="' + esc(s.name) + '" data-action="remove">Remove</button>';
|
|
2347
|
+
// Per-app on/off chips (Claude Code, Codex, OpenCode...). Only for an enabled enigma
|
|
2348
|
+
// skill: when the whole skill is disabled the per-agent state is moot. data-off is the
|
|
2349
|
+
// value to SET on click (turn off if currently on, on if currently off).
|
|
2350
|
+
const agentChips = (s.source === "enigma" && !s.discarded && s.agentStates && s.agentStates.length)
|
|
2351
|
+
? '<div class="set-agents">' + s.agentStates.map((a) =>
|
|
2352
|
+
'<button type="button" class="achip' + (a.deployed ? " on" : "") + '" data-action="agent-toggle" data-name="' + esc(s.name)
|
|
2353
|
+
+ '" data-agent="' + esc(a.name) + '" data-off="' + (a.deployed ? 1 : 0) + '" title="'
|
|
2354
|
+
+ (a.deployed ? "On for " + esc(a.label) + " - click to disable" : "Off for " + esc(a.label) + " - click to enable")
|
|
2355
|
+
+ '">' + esc(a.label) + (a.deployed ? " on" : " off") + "</button>").join("") + "</div>"
|
|
2356
|
+
: "";
|
|
2287
2357
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + upd + where + "</div>"
|
|
2288
|
-
+ '<div class="set-hint">' + esc(s.description || "") + '</div
|
|
2358
|
+
+ '<div class="set-hint">' + esc(s.description || "") + "</div>" + agentChips + '</div><div class="set-ctl" style="display:flex;gap:8px">' + edit + main + "</div></div>";
|
|
2289
2359
|
}
|
|
2290
2360
|
|
|
2291
2361
|
function renderSkills(skills) {
|
|
@@ -2299,10 +2369,10 @@ export {
|
|
|
2299
2369
|
+ section("External skills", "Skills enigma did not author, found in your agents. Remove deletes the skill from every agent that has it.", ext, "No external skills found.");
|
|
2300
2370
|
}
|
|
2301
2371
|
|
|
2302
|
-
async function postSkill(name, action) {
|
|
2372
|
+
async function postSkill(name, action, content) {
|
|
2303
2373
|
$("skillsNote").textContent = "Working...";
|
|
2304
2374
|
try {
|
|
2305
|
-
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action }) });
|
|
2375
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action, content }) });
|
|
2306
2376
|
const out = await res.json();
|
|
2307
2377
|
if (!out.ok) { $("skillsNote").textContent = "Could not " + action + " " + name + ": " + (out.error || "error"); return; }
|
|
2308
2378
|
if (out.skills) renderSkills(out.skills);
|
|
@@ -2348,6 +2418,7 @@ export {
|
|
|
2348
2418
|
if (!b) return;
|
|
2349
2419
|
const { name, action } = b.dataset;
|
|
2350
2420
|
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
2421
|
+
if (action === "agent-toggle") { postSkill(name, action, { agent: b.dataset.agent, off: b.dataset.off === "1" }); return; }
|
|
2351
2422
|
if (action === "remove" && !(await askConfirm("Remove skill", 'Remove the external skill "' + name + '" from all agents? This deletes it from disk.', true))) return;
|
|
2352
2423
|
postSkill(name, action);
|
|
2353
2424
|
});
|
|
@@ -2507,9 +2578,11 @@ export {
|
|
|
2507
2578
|
initChart();
|
|
2508
2579
|
poll(true);
|
|
2509
2580
|
loadClaudeStatus();
|
|
2581
|
+
checkDashboardUpdate();
|
|
2510
2582
|
setInterval(() => refreshAll(false), POLL_MS);
|
|
2511
2583
|
setInterval(loadClaudeStatus, 60000);
|
|
2512
|
-
|
|
2584
|
+
setInterval(checkDashboardUpdate, UPDATE_CHECK_MS);
|
|
2585
|
+
document.addEventListener("visibilitychange", () => { if (!document.hidden) { refreshAll(true); loadClaudeStatus(); checkDashboardUpdate(); } });
|
|
2513
2586
|
wireSettings();
|
|
2514
2587
|
wireSkills();
|
|
2515
2588
|
wireAccounts();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
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": [
|