@enigmax/dashboard 0.1.68 → 0.1.70

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 +103 -7
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -526,6 +526,7 @@
526
526
  <a href="#/settings" data-view="settings" class="tab">Settings</a>
527
527
  <a href="#/resources" data-view="resources" class="tab">Resources</a>
528
528
  <a href="#/recall" data-view="recall" class="tab">Recall</a>
529
+ <a href="#/codegraph" data-view="codegraph" class="tab">Code graph</a>
529
530
  <a href="#/packs" data-view="packs" class="tab">Packs</a>
530
531
  </nav>
531
532
  <div class="nav">
@@ -873,6 +874,19 @@
873
874
  <div id="recallList"></div>
874
875
  </section>
875
876
 
877
+ <section id="view-codegraph" style="display:none">
878
+ <div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
879
+ <div style="flex:1">
880
+ <h1>Code graph</h1>
881
+ <p>Structural memory of your codebase - a knowledge graph of symbols, call chains, and routes. Enabling it registers the code-intelligence server in your agents so they can query the graph over MCP. Complements <a href="#/recall">Recall</a> (session memory).</p>
882
+ </div>
883
+ <button id="cgToggle" type="button" class="toggle" role="switch" aria-checked="false" aria-label="Codebase memory">off</button>
884
+ <button id="cgRefresh" 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>
885
+ </div>
886
+ <div id="cgNote" class="set-note"></div>
887
+ <div id="cgBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
888
+ </section>
889
+
876
890
  <footer>
877
891
  Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
878
892
  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>
@@ -3156,6 +3170,7 @@ export {
3156
3170
  const guard = (sec.guardProtects || []).map((p) => '<span class="tag">' + esc(p) + "</span>").join("");
3157
3171
  el.innerHTML =
3158
3172
  item("Context compression (MCP) · measured", boolPill(s.compress))
3173
+ + item("Codebase memory / code graph (MCP)", boolPill(s.codeGraph))
3159
3174
  + item("Real tool-usage stats · measured", boolPill(s.usageStats))
3160
3175
  + item("Claude Code proxy · experimental", boolPill(s.proxy))
3161
3176
  + item("Live usage windows · real", boolPill(s.usageApi))
@@ -3503,8 +3518,11 @@ export {
3503
3518
  const TRASH_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>';
3504
3519
  const REFRESH_ICON = '<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>';
3505
3520
  function accountRow(a) {
3521
+ const stateTxt = a.loginState === "expired" ? "session expired" : a.loginState === "empty" ? "signed out" : a.loginState === "absent" ? "not logged in" : "";
3506
3522
  const badges = (a.active ? '<span class="tag meas">active</span>' : "")
3507
- + (a.loggedIn ? '<span class="tag">' + esc(a.email || "logged in") + "</span>" : '<span class="tag warn">not logged in</span>');
3523
+ + (a.loggedIn
3524
+ ? '<span class="tag">' + esc(a.email || "logged in") + "</span>"
3525
+ : '<span class="tag warn">' + (a.email ? esc(a.email) + " · " : "") + (stateTxt || "not logged in") + "</span>");
3508
3526
  const btn = (act, label, cls) => '<button type="button" class="' + (cls || "toggle") + '" data-acc="' + act + '" data-tool="' + esc(a.tool) + '" data-name="' + esc(a.name) + '">' + label + "</button>";
3509
3527
  const removeBtn = '<button type="button" class="btn-danger icon" title="Remove" aria-label="Remove account ' + esc(a.name) + '" data-acc="remove" data-tool="' + esc(a.tool) + '" data-name="' + esc(a.name) + '">' + TRASH_ICON + "</button>";
3510
3528
  // Provider override (e.g. MiniMax) only for tools that support it, and never the
@@ -3512,7 +3530,7 @@ export {
3512
3530
  const provBtn = (a.supportsProvider && a.removable) ? btn("provider", "Provider") : "";
3513
3531
  const acts = '<div class="set-ctl" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">'
3514
3532
  + (a.active ? "" : btn("activate", "Use"))
3515
- + (a.loggedIn ? "" : btn("login", "Log in..."))
3533
+ + btn("login", a.loggedIn ? "Re-login" : "Log in")
3516
3534
  + provBtn
3517
3535
  + (a.removable ? btn("rename", "Rename") + removeBtn : "")
3518
3536
  + "</div>";
@@ -3560,7 +3578,7 @@ export {
3560
3578
  const r = await fetch("/api/accounts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(Object.assign({ op }, payload)) });
3561
3579
  const out = await r.json();
3562
3580
  if (out.data) renderAccounts(out.data);
3563
- if (out.ok) { setNote("accountsNote", "Saved.", "ok"); return; }
3581
+ if (out.ok) { setNote("accountsNote", out.note || "Saved.", "ok"); return; }
3564
3582
  if (rollback && !out.data) rollback();
3565
3583
  setNote("accountsNote", "Error: " + (out.error || "failed"), "err");
3566
3584
  } catch { if (rollback) rollback(); setNote("accountsNote", "Could not reach the server.", "err"); }
@@ -3644,7 +3662,7 @@ export {
3644
3662
  if (op === "activate") accAction("account.activate", { tool, name });
3645
3663
  else if (op === "remove") { if (await askConfirm("Remove account", "Remove account '" + name + "' and delete its local config dir?", true)) accAction("account.remove", { tool, name }); }
3646
3664
  else if (op === "rename") { const taken = accAccounts.filter((a) => a.tool === tool).map((a) => a.name); const nn = await askText("Rename account", name, "Rename '" + name + "' to:", nameValidator(taken, name)); if (nn && nn !== name) accAction("account.rename", { tool, name, newName: nn }); }
3647
- else if (op === "login") { setNote("accountsNote", "To log in, run this in a terminal: enigma " + tool + " " + name); }
3665
+ else if (op === "login") accAction("account.login", { tool, name });
3648
3666
  else if (op === "provider") {
3649
3667
  const a = accAccounts.find((x) => x.tool === tool && x.name === name);
3650
3668
  if (!a) return;
@@ -3710,7 +3728,7 @@ export {
3710
3728
 
3711
3729
  // --- hash routing between the subpages ---
3712
3730
  // Single source of truth for the subpage ids (savings is the default/fallback).
3713
- const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources", "recall", "packs"];
3731
+ const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources", "recall", "codegraph", "packs"];
3714
3732
  // Views that apply to a single project; the rest are global/machine-level only.
3715
3733
  const PROJECT_VIEWS = ["settings", "skills"];
3716
3734
  let activeView = "savings";
@@ -3731,6 +3749,7 @@ export {
3731
3749
  else if (v === "usage") { loadProviderStatus(); if (!usageAccountsLoaded) { usageAccountsLoaded = true; loadUsageAccounts(); } }
3732
3750
  else if (v === "resources") loadResources();
3733
3751
  else if (v === "recall") loadRecall();
3752
+ else if (v === "codegraph") loadCodeGraph();
3734
3753
  else if (v === "packs") loadPacks();
3735
3754
  // The chart was sized while its view may have been hidden; nudge it on return.
3736
3755
  else if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
@@ -3808,9 +3827,9 @@ export {
3808
3827
  const stTag = (s) => s === "expired" ? " (login expired)" : s === "empty" ? " (not signed in)" : s === "absent" ? " (no login)" : "";
3809
3828
  const opts = '<option value="">follow active (' + esc(p.resolvedAccount || "default") + ")" + stTag(p.resolvedState) + "</option>"
3810
3829
  + (p.accounts || []).map((a) => '<option value="' + esc(a.name) + '"' + (p.defaultAccount === a.name ? " selected" : "") + ">" + esc(a.name) + (a.label && a.label !== a.name ? " - " + esc(a.label) : "") + stTag(a.state) + "</option>").join("");
3811
- const warn = p.installed && (p.resolvedState === "expired" || p.resolvedState === "empty")
3830
+ const warn = (p.installed && !p.contextReady && (p.resolvedState === "expired" || p.resolvedState === "empty"))
3812
3831
  ? '<div class="set-hint" style="margin-top:6px;color:var(--warn,#e0a020)">This login is ' + (p.resolvedState === "expired" ? "expired" : "not signed in") + ' - the pack may ask you to log in. Refresh it (launch that account once) or pick another above.</div>'
3813
- : "";
3832
+ : (p.installed && p.contextReady ? '<div class="set-hint" style="margin-top:6px;color:var(--ok)">Active session ready - launches without asking to log in.</div>' : "");
3814
3833
  const accountSel = p.installed
3815
3834
  ? '<label class="set-hint" style="display:flex;align-items:center;gap:8px;margin-top:10px">Account that seeds this pack:'
3816
3835
  + '<select data-pack-account data-id="' + esc(p.id) + '">' + opts + "</select></label>" + warn
@@ -3854,6 +3873,82 @@ export {
3854
3873
  });
3855
3874
  }
3856
3875
 
3876
+ // --- code graph subpage (structural code memory) ---
3877
+ let cgProject = null;
3878
+ // A single architecture/schema field, rendered defensively (shape varies by tool version).
3879
+ function cgItem(x) {
3880
+ if (x && typeof x === "object") return String(x.name || x.label || x.path || x.id || JSON.stringify(x)).slice(0, 60);
3881
+ return String(x);
3882
+ }
3883
+ function cgVal(v) {
3884
+ if (Array.isArray(v)) {
3885
+ if (!v.length) return '<span class="tag">none</span>';
3886
+ return v.slice(0, 12).map((x) => '<span class="tag">' + esc(cgItem(x)) + "</span>").join(" ")
3887
+ + (v.length > 12 ? ' <span class="set-hint">+' + (v.length - 12) + " more</span>" : "");
3888
+ }
3889
+ if (v && typeof v === "object") return '<span class="set-hint">' + esc(Object.keys(v).length + " field(s)") + "</span>";
3890
+ return esc(String(v));
3891
+ }
3892
+ function cgSection(title, obj) {
3893
+ const entries = obj && typeof obj === "object" ? Object.entries(obj) : [];
3894
+ if (!entries.length) return "";
3895
+ return '<div class="panel"><div class="set-cat-h">' + esc(title) + "</div>"
3896
+ + entries.map(([k, v]) => '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(k) + '</div><div class="set-hint">' + cgVal(v) + "</div></div></div>").join("")
3897
+ + "</div>";
3898
+ }
3899
+ function renderCodeGraph(view) {
3900
+ view = view || {};
3901
+ const on = !!view.enabled;
3902
+ const t = $("cgToggle");
3903
+ t.classList.toggle("on", on); t.setAttribute("aria-checked", on ? "true" : "false"); t.textContent = on ? "on" : "off";
3904
+ if (!view.available) {
3905
+ $("cgBody").innerHTML = '<div class="panel"><div class="set-cat-h">Not installed</div>'
3906
+ + '<div class="set-hint" style="padding:6px 0">The code-intelligence engine is not available yet. When enabled, your agent fetches it automatically on first use (needs <code>npx</code> on PATH). Then tell your agent "Index this project".</div></div>';
3907
+ return;
3908
+ }
3909
+ const projects = view.projects || [];
3910
+ const projHtml = projects.length
3911
+ ? projects.map((p) => {
3912
+ const name = p.name || p.root || "";
3913
+ const sel = name === view.selected;
3914
+ return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(name)
3915
+ + (sel ? ' <span class="tag" style="color:var(--ok)">viewing</span>' : "")
3916
+ + '</div><div class="set-hint">' + esc(p.root || "") + '</div></div>'
3917
+ + '<div class="set-ctl"><button type="button" class="toggle' + (sel ? " on" : "") + '" data-cg-project="' + esc(name) + '">View</button></div></div>';
3918
+ }).join("")
3919
+ : '<div class="empty" style="padding:12px 0">No projects indexed yet. In your agent, say "Index this project".</div>';
3920
+ let html = '<div class="panel"><div class="set-cat-h">Indexed projects <small>' + projects.length + "</small></div>" + projHtml + "</div>";
3921
+ if (view.architecture) html += cgSection("Architecture" + (view.selected ? " - " + view.selected : ""), view.architecture);
3922
+ if (view.schema) html += cgSection("Graph schema", view.schema);
3923
+ $("cgBody").innerHTML = html;
3924
+ }
3925
+ async function loadCodeGraph() {
3926
+ $("cgNote").textContent = "";
3927
+ try {
3928
+ const q = cgProject ? "?project=" + encodeURIComponent(cgProject) : "";
3929
+ renderCodeGraph(await (await fetch("/api/codegraph" + q, { cache: "no-store" })).json());
3930
+ } catch { $("cgBody").innerHTML = '<div class="empty" style="padding:12px 0">Code graph unavailable.</div>'; }
3931
+ }
3932
+ async function cgToggleAction() {
3933
+ const turnOn = $("cgToggle").getAttribute("aria-checked") !== "true";
3934
+ setNote("cgNote", "Saving...");
3935
+ try {
3936
+ const out = await (await fetch("/api/codegraph", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op: "toggle", on: turnOn, project: cgProject || undefined }) })).json();
3937
+ if (out.view) renderCodeGraph(out.view);
3938
+ setNote("cgNote", out.ok ? (turnOn ? "Codebase memory enabled - registered in your agents (restart them to load it)." : "Codebase memory disabled.") : (out.error || "Action failed."), out.ok ? "ok" : "err");
3939
+ } catch { setNote("cgNote", "Could not reach the server.", "err"); }
3940
+ }
3941
+ function wireCodeGraph() {
3942
+ $("cgRefresh").addEventListener("click", loadCodeGraph);
3943
+ $("cgToggle").addEventListener("click", cgToggleAction);
3944
+ $("view-codegraph").addEventListener("click", (e) => {
3945
+ const b = e.target.closest("button[data-cg-project]");
3946
+ if (!b) return;
3947
+ cgProject = b.dataset.cgProject;
3948
+ loadCodeGraph();
3949
+ });
3950
+ }
3951
+
3857
3952
  // --- recall subpage (local session memory; reads your own transcripts) ---
3858
3953
  const RECALL_TYPES = ["bugfix", "feature", "refactor", "change", "discovery", "decision", "security"];
3859
3954
  let recallProjSig = "";
@@ -4422,6 +4517,7 @@ export {
4422
4517
  wireUsage();
4423
4518
  wireResources();
4424
4519
  wireRecall();
4520
+ wireCodeGraph();
4425
4521
  wirePacks();
4426
4522
  wireScope();
4427
4523
  enhanceAll(); // upgrade every static <select> into the shared pretty dropdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
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": [