@enigmax/dashboard 0.1.55 → 0.1.58
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 +145 -13
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -426,6 +426,7 @@
|
|
|
426
426
|
<a href="#/skills" data-view="skills" class="tab">Skills</a>
|
|
427
427
|
<a href="#/settings" data-view="settings" class="tab">Settings</a>
|
|
428
428
|
<a href="#/resources" data-view="resources" class="tab">Resources</a>
|
|
429
|
+
<a href="#/recall" data-view="recall" class="tab">Recall</a>
|
|
429
430
|
</nav>
|
|
430
431
|
<div class="nav">
|
|
431
432
|
<label class="scope-wrap" title="Scope: global config or a specific project"><span class="scope-lbl">Scope</span><select id="scopeSel" class="scope-sel"><option value="global">Global</option></select></label>
|
|
@@ -696,6 +697,28 @@
|
|
|
696
697
|
<div class="panel"><div class="set-cat-h">Processes <small>by memory</small></div><div id="resProcs"><div class="empty" style="padding:12px 0">Loading...</div></div></div>
|
|
697
698
|
</section>
|
|
698
699
|
|
|
700
|
+
<section id="view-recall" style="display:none">
|
|
701
|
+
<div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
|
|
702
|
+
<div style="flex:1">
|
|
703
|
+
<h1>Recall</h1>
|
|
704
|
+
<p>Searchable memory of your past coding sessions, built locally from your own transcripts. Agents can query it over MCP (<code>enigma_recall</code>); it never leaves this machine.</p>
|
|
705
|
+
</div>
|
|
706
|
+
<button id="recallSync" type="button" class="toggle on" title="Read new transcripts into the store">Sync now</button>
|
|
707
|
+
<button id="recallClear" type="button" class="btn-danger" title="Delete all stored memory">Clear</button>
|
|
708
|
+
</div>
|
|
709
|
+
<div id="recallNote" class="set-note"></div>
|
|
710
|
+
<div id="recallBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
|
|
711
|
+
<div id="recallSearch" class="panel" style="display:none">
|
|
712
|
+
<div class="set-cat-h">Search</div>
|
|
713
|
+
<div class="ladd" style="gap:8px;flex-wrap:wrap">
|
|
714
|
+
<input id="recallQ" class="linput" type="search" placeholder="Search your session memory..." style="flex:1;min-width:220px">
|
|
715
|
+
<select id="recallType" class="linput" style="max-width:160px"></select>
|
|
716
|
+
<select id="recallProject" class="linput" style="max-width:200px"></select>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
<div id="recallList"></div>
|
|
720
|
+
</section>
|
|
721
|
+
|
|
699
722
|
<footer>
|
|
700
723
|
Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
|
|
701
724
|
Data source: <code>~/.enigma/ccr/stats.json</code> + <code>history.jsonl</code> - reset it with <code>enigma compress --clear</code>. Enigma <span id="ver"></span>
|
|
@@ -2892,16 +2915,16 @@ export {
|
|
|
2892
2915
|
let skillScope = "global"; // "global" | "project" - the same rows render in both
|
|
2893
2916
|
function skillRow(s) {
|
|
2894
2917
|
const ver = s.version ? '<span class="tag">v' + esc(s.version) + "</span>" : "";
|
|
2895
|
-
// Project scope: same row, but the per-app chips
|
|
2896
|
-
//
|
|
2918
|
+
// Project scope: same row, but policy skills are managed globally - the per-app chips are
|
|
2919
|
+
// read-only here (show presence only) and the global-only controls are hidden.
|
|
2897
2920
|
if (skillScope === "project") {
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2921
|
+
// Policy (enigma) skills are managed at GLOBAL scope - deploying one per-project is
|
|
2922
|
+
// redundant (an agent reads both global and project skills). So in project scope the
|
|
2923
|
+
// per-app chips are READ-ONLY: they show where the skill is present but never toggle
|
|
2924
|
+
// (no data-pskill). Switch Scope to Global to enable/disable a policy skill.
|
|
2925
|
+
const chips = (s.agentStates || []).map((a) =>
|
|
2926
|
+
'<button type="button" class="achip' + (a.deployed ? " on" : "") + '" disabled aria-label="' + esc(a.label) + '" title="'
|
|
2927
|
+
+ esc(a.label) + (a.installed === false ? " - not installed" : " - policy skills are managed at Global scope") + '">' + (agentSvg(a.name) || esc(a.label)) + "</button>").join("");
|
|
2905
2928
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ver + "</div>"
|
|
2906
2929
|
+ '<div class="set-hint">' + esc(s.description || "") + "</div>"
|
|
2907
2930
|
+ '<div class="set-agents">' + (chips || '<span class="tag">no agents</span>') + "</div></div></div>";
|
|
@@ -2962,7 +2985,7 @@ export {
|
|
|
2962
2985
|
'<div class="panel"><div class="set-cat-h">Auto-detect skills by stack</div>'
|
|
2963
2986
|
+ '<div class="set-cat-b">Scan this project (packages, config files, gems) and install the matching community skills for its stack - hash-verified and kept separate from the policy skills below.</div>'
|
|
2964
2987
|
+ '<div class="ladd"><button type="button" class="toggle on with-icon" data-proj-autoskills><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>Detect skills</button></div></div>'
|
|
2965
|
-
+ section("Policy skills", "
|
|
2988
|
+
+ section("Policy skills", "enigma's policy skills are managed at Global scope (an agent reads them in every project). Shown here read-only - a green app logo means the skill is also present in this project's folder. Switch Scope to Global to enable or disable a policy skill.", skills, "No skills.");
|
|
2966
2989
|
return;
|
|
2967
2990
|
}
|
|
2968
2991
|
const enigma = skills.filter((s) => s.source === "enigma");
|
|
@@ -3237,7 +3260,7 @@ export {
|
|
|
3237
3260
|
|
|
3238
3261
|
// --- hash routing between the subpages ---
|
|
3239
3262
|
// Single source of truth for the subpage ids (savings is the default/fallback).
|
|
3240
|
-
const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources"];
|
|
3263
|
+
const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources", "recall"];
|
|
3241
3264
|
// Views that apply to a single project; the rest are global/machine-level only.
|
|
3242
3265
|
const PROJECT_VIEWS = ["settings", "skills"];
|
|
3243
3266
|
let activeView = "savings";
|
|
@@ -3257,6 +3280,7 @@ export {
|
|
|
3257
3280
|
else if (v === "accounts") { if (!accountsLoaded) { accountsLoaded = true; loadAccounts(); } }
|
|
3258
3281
|
else if (v === "usage") { loadProviderStatus(); if (!usageAccountsLoaded) { usageAccountsLoaded = true; loadUsageAccounts(); } }
|
|
3259
3282
|
else if (v === "resources") loadResources();
|
|
3283
|
+
else if (v === "recall") loadRecall();
|
|
3260
3284
|
// The chart was sized while its view may have been hidden; nudge it on return.
|
|
3261
3285
|
else if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
|
|
3262
3286
|
}
|
|
@@ -3316,6 +3340,115 @@ export {
|
|
|
3316
3340
|
});
|
|
3317
3341
|
}
|
|
3318
3342
|
|
|
3343
|
+
// --- recall subpage (local session memory; reads your own transcripts) ---
|
|
3344
|
+
const RECALL_TYPES = ["bugfix", "feature", "refactor", "change", "discovery", "decision", "security"];
|
|
3345
|
+
let recallProjSig = "";
|
|
3346
|
+
function renderRecall(v) {
|
|
3347
|
+
v = v || {};
|
|
3348
|
+
if (!v.available) {
|
|
3349
|
+
$("recallBody").innerHTML = '<div class="panel"><div class="empty" style="padding:16px 0">Recall needs the enigma binary - open the dashboard via the enigma app.</div></div>';
|
|
3350
|
+
$("recallSearch").style.display = "none"; $("recallList").innerHTML = ""; return;
|
|
3351
|
+
}
|
|
3352
|
+
if (!v.enabled) {
|
|
3353
|
+
$("recallBody").innerHTML = '<div class="panel"><div class="set-cat-h">Session memory is off</div>'
|
|
3354
|
+
+ '<div class="set-hint" style="padding:8px 0">Build a searchable memory of your coding sessions from your own transcripts. Turn it on with:</div>'
|
|
3355
|
+
+ '<code>enigma config recall on</code></div>';
|
|
3356
|
+
$("recallSearch").style.display = "none"; $("recallList").innerHTML = ""; return;
|
|
3357
|
+
}
|
|
3358
|
+
const s = v.stats || {};
|
|
3359
|
+
const types = Object.entries(s.byType || {}).map(([t, n]) => '<span class="tag">' + esc(t) + " " + n + "</span>").join(" ");
|
|
3360
|
+
const last = v.lastSync ? "last sync " + new Date(v.lastSync).toLocaleString() : "not synced yet";
|
|
3361
|
+
$("recallBody").innerHTML = '<div class="panel"><div class="set-cat-h">Stored memory</div>'
|
|
3362
|
+
+ '<div class="set-hint" style="padding:6px 0">' + (s.observations || 0) + " observations · " + (s.sessions || 0) + " sessions · "
|
|
3363
|
+
+ (s.projects || 0) + " projects · " + Math.round((s.dbBytes || 0) / 1024) + " KB · " + esc(last) + "</div>"
|
|
3364
|
+
+ (types ? '<div style="padding-top:6px">' + types + "</div>" : "") + "</div>";
|
|
3365
|
+
$("recallSearch").style.display = "";
|
|
3366
|
+
if (!$("recallType").options.length) $("recallType").innerHTML = '<option value="">All types</option>' + RECALL_TYPES.map((t) => '<option value="' + t + '">' + t + "</option>").join("");
|
|
3367
|
+
const sig = (v.projects || []).join("|");
|
|
3368
|
+
if (sig !== recallProjSig) {
|
|
3369
|
+
const keep = $("recallProject").value;
|
|
3370
|
+
$("recallProject").innerHTML = '<option value="">All projects</option>' + (v.projects || []).map((p) => '<option value="' + esc(p) + '">' + esc(p) + "</option>").join("");
|
|
3371
|
+
$("recallProject").value = keep;
|
|
3372
|
+
recallProjSig = sig;
|
|
3373
|
+
}
|
|
3374
|
+
renderRecallList(v.items);
|
|
3375
|
+
}
|
|
3376
|
+
let recallItemsById = {};
|
|
3377
|
+
function recallRowHtml(it, anchorId) {
|
|
3378
|
+
const ic = '<span class="aicon" title="' + esc(it.source) + '" aria-label="' + esc(it.source) + '">' + (agentSvg(it.source) || esc(it.source)) + "</span>";
|
|
3379
|
+
const date = it.createdAt ? new Date(it.createdAt).toISOString().slice(0, 10) : "";
|
|
3380
|
+
const mark = (anchorId && it.id === anchorId) ? ' style="border-left:2px solid var(--accent,#888);padding-left:8px"' : "";
|
|
3381
|
+
return '<div class="set-meta"' + mark + '><div class="set-name">' + ic + ' <span class="tag">' + esc(it.type) + "</span> " + esc(it.title) + "</div>"
|
|
3382
|
+
+ '<div class="set-hint" style="opacity:.6">' + esc(it.project) + " · " + date + (it.id ? " · #" + it.id : "") + "</div></div>";
|
|
3383
|
+
}
|
|
3384
|
+
function recallDetailHtml(it) {
|
|
3385
|
+
const tags = (a) => (a && a.length) ? a.map((x) => '<span class="tag">' + esc(x) + "</span>").join(" ") : "";
|
|
3386
|
+
const list = (label, a) => (a && a.length) ? '<div class="set-hint"><strong>' + label + ":</strong> " + esc(a.join(", ")) + "</div>" : "";
|
|
3387
|
+
const facts = (it.facts && it.facts.length) ? "<ul style='margin:4px 0 0 16px'>" + it.facts.map((f) => "<li>" + esc(f) + "</li>").join("") + "</ul>" : "";
|
|
3388
|
+
return '<div style="padding:8px 0 4px 28px">'
|
|
3389
|
+
+ (it.narrative ? '<div style="margin-bottom:6px">' + esc(it.narrative) + "</div>" : "")
|
|
3390
|
+
+ facts
|
|
3391
|
+
+ (it.concepts && it.concepts.length ? '<div style="margin-top:6px">' + tags(it.concepts) + "</div>" : "")
|
|
3392
|
+
+ list("Modified", it.files) + list("Read", it.filesRead)
|
|
3393
|
+
+ '<div style="margin-top:8px"><button type="button" class="toggle" data-tl="' + it.id + '">Show timeline</button></div>'
|
|
3394
|
+
+ '<div class="rtl" id="rtl-' + it.id + '"></div></div>';
|
|
3395
|
+
}
|
|
3396
|
+
function renderRecallList(items) {
|
|
3397
|
+
recallItemsById = {};
|
|
3398
|
+
if (!items || !items.length) { $("recallList").innerHTML = '<div class="empty" style="padding:16px 0">No memories yet - run a sync, or adjust your search.</div>'; return; }
|
|
3399
|
+
$("recallList").innerHTML = '<div class="panel">' + items.map((it) => {
|
|
3400
|
+
if (it.id) recallItemsById[it.id] = it;
|
|
3401
|
+
return '<div class="set-row" style="cursor:pointer" data-rid="' + (it.id || "") + '">' + recallRowHtml(it) + "</div>"
|
|
3402
|
+
+ '<div id="rd-' + (it.id || "") + '" style="display:none"></div>';
|
|
3403
|
+
}).join("") + "</div>";
|
|
3404
|
+
}
|
|
3405
|
+
async function loadRecallTimeline(id) {
|
|
3406
|
+
const el = $("rtl-" + id);
|
|
3407
|
+
if (!el) return;
|
|
3408
|
+
el.innerHTML = '<div class="set-hint" style="padding:6px 0">Loading timeline...</div>';
|
|
3409
|
+
try {
|
|
3410
|
+
const out = await (await fetch("/api/recall?timeline=" + id, { cache: "no-store" })).json();
|
|
3411
|
+
const rows = out.items || [];
|
|
3412
|
+
el.innerHTML = rows.length
|
|
3413
|
+
? '<div style="margin-top:6px;border-top:1px solid rgba(128,128,128,.2)">' + rows.map((r) => '<div class="set-row">' + recallRowHtml(r, Number(id)) + "</div>").join("") + "</div>"
|
|
3414
|
+
: '<div class="set-hint">No surrounding context.</div>';
|
|
3415
|
+
} catch { el.innerHTML = '<div class="set-hint err">Could not load timeline.</div>'; }
|
|
3416
|
+
}
|
|
3417
|
+
async function loadRecall() {
|
|
3418
|
+
const q = encodeURIComponent($("recallQ").value || "");
|
|
3419
|
+
const type = encodeURIComponent($("recallType").value || "");
|
|
3420
|
+
const proj = encodeURIComponent($("recallProject").value || "");
|
|
3421
|
+
try { renderRecall(await (await fetch("/api/recall?q=" + q + "&type=" + type + "&project=" + proj, { cache: "no-store" })).json()); }
|
|
3422
|
+
catch { $("recallBody").innerHTML = '<div class="empty" style="padding:12px 0">Recall unavailable.</div>'; }
|
|
3423
|
+
}
|
|
3424
|
+
async function recallAction(op, confirmMsg) {
|
|
3425
|
+
if (confirmMsg && !(await askConfirm("Confirm", confirmMsg, true))) return;
|
|
3426
|
+
setNote("recallNote", op === "sync" ? "Reading transcripts..." : "Working...");
|
|
3427
|
+
try {
|
|
3428
|
+
const out = await (await fetch("/api/recall", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op }) })).json();
|
|
3429
|
+
if (out.view) renderRecall(out.view);
|
|
3430
|
+
setNote("recallNote", out.ok ? (op === "sync" ? "Synced." : "Cleared.") : (out.error || "Action failed."), out.ok ? "ok" : "err");
|
|
3431
|
+
} catch { setNote("recallNote", "Could not reach the server.", "err"); }
|
|
3432
|
+
}
|
|
3433
|
+
function wireRecall() {
|
|
3434
|
+
$("recallSync").addEventListener("click", () => recallAction("sync"));
|
|
3435
|
+
$("recallClear").addEventListener("click", () => recallAction("clear", "Delete all stored session memory? This cannot be undone."));
|
|
3436
|
+
let t; const deb = () => { clearTimeout(t); t = setTimeout(loadRecall, 250); };
|
|
3437
|
+
$("recallQ").addEventListener("input", deb);
|
|
3438
|
+
$("recallType").addEventListener("change", loadRecall);
|
|
3439
|
+
$("recallProject").addEventListener("change", loadRecall);
|
|
3440
|
+
$("recallList").addEventListener("click", (e) => {
|
|
3441
|
+
const tl = e.target.closest("[data-tl]");
|
|
3442
|
+
if (tl) { loadRecallTimeline(tl.dataset.tl); return; }
|
|
3443
|
+
const row = e.target.closest("[data-rid]");
|
|
3444
|
+
if (!row || !row.dataset.rid) return;
|
|
3445
|
+
const detail = $("rd-" + row.dataset.rid);
|
|
3446
|
+
if (!detail) return;
|
|
3447
|
+
if (detail.innerHTML === "") detail.innerHTML = recallDetailHtml(recallItemsById[row.dataset.rid]);
|
|
3448
|
+
detail.style.display = detail.style.display === "none" ? "" : "none";
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3319
3452
|
// --- scope (global vs a project) ---------------------------------------------
|
|
3320
3453
|
// A header selector switches the whole dashboard between Global and a specific
|
|
3321
3454
|
// project. Project scope is loopback-only and only ever touches a registered
|
|
@@ -3572,8 +3705,6 @@ export {
|
|
|
3572
3705
|
function onProjectControlClick(e) {
|
|
3573
3706
|
if (!isProjectScope()) return;
|
|
3574
3707
|
if (e.target.closest("[data-proj-autoskills]")) { autoskillsFlow(); return; }
|
|
3575
|
-
const chip = e.target.closest("[data-pskill]"); // per-app deploy chip in the Skills view
|
|
3576
|
-
if (chip) { projAction({ op: "skill", name: chip.dataset.pskill, agent: chip.dataset.pagent, on: chip.dataset.poff !== "1" }); return; }
|
|
3577
3708
|
const sk = e.target.closest("[data-proj-skill]");
|
|
3578
3709
|
if (sk) { projAction({ op: "skill", name: sk.dataset.projSkill, on: sk.dataset.on !== "1" }); return; }
|
|
3579
3710
|
const cb = e.target.closest("[data-proj-cfg-bool]");
|
|
@@ -3609,6 +3740,7 @@ export {
|
|
|
3609
3740
|
wireAccounts();
|
|
3610
3741
|
wireUsage();
|
|
3611
3742
|
wireResources();
|
|
3743
|
+
wireRecall();
|
|
3612
3744
|
wireScope();
|
|
3613
3745
|
$("cfgExport").addEventListener("click", exportConfig);
|
|
3614
3746
|
$("cfgImport").addEventListener("click", importConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
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": [
|