@enigmax/dashboard 0.1.69 → 0.1.71

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 +107 -1
  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,24 @@
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 native knowledge graph of the symbols each file defines, the modules it imports and cross-file references. Enabling it exposes enigma's code-graph tools to your agents over MCP. Complements <a href="#/recall">Recall</a> (session memory).</p>
882
+ </div>
883
+ <button id="cgToggle" type="button" class="toggle" data-kind="bool" role="switch" aria-checked="false" aria-label="Code graph">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 class="panel">
888
+ <div class="set-cat-h">Index a project</div>
889
+ <div class="set-hint" style="padding:4px 0">Build the graph for a codebase. Leave the path empty to index enigma's own working directory.</div>
890
+ <div class="ladd" style="gap:8px;flex-wrap:wrap"><input id="cgRoot" class="linput" placeholder="absolute project path (optional)" style="flex:1;min-width:240px"><button id="cgIndex" type="button" class="toggle on">Index</button></div>
891
+ </div>
892
+ <div id="cgBody"><div class="empty" style="padding:12px 0">Loading...</div></div>
893
+ </section>
894
+
876
895
  <footer>
877
896
  Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
878
897
  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 +3175,7 @@ export {
3156
3175
  const guard = (sec.guardProtects || []).map((p) => '<span class="tag">' + esc(p) + "</span>").join("");
3157
3176
  el.innerHTML =
3158
3177
  item("Context compression (MCP) · measured", boolPill(s.compress))
3178
+ + item("Codebase memory / code graph (MCP)", boolPill(s.codeGraph))
3159
3179
  + item("Real tool-usage stats · measured", boolPill(s.usageStats))
3160
3180
  + item("Claude Code proxy · experimental", boolPill(s.proxy))
3161
3181
  + item("Live usage windows · real", boolPill(s.usageApi))
@@ -3713,7 +3733,7 @@ export {
3713
3733
 
3714
3734
  // --- hash routing between the subpages ---
3715
3735
  // Single source of truth for the subpage ids (savings is the default/fallback).
3716
- const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources", "recall", "packs"];
3736
+ const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources", "recall", "codegraph", "packs"];
3717
3737
  // Views that apply to a single project; the rest are global/machine-level only.
3718
3738
  const PROJECT_VIEWS = ["settings", "skills"];
3719
3739
  let activeView = "savings";
@@ -3734,6 +3754,7 @@ export {
3734
3754
  else if (v === "usage") { loadProviderStatus(); if (!usageAccountsLoaded) { usageAccountsLoaded = true; loadUsageAccounts(); } }
3735
3755
  else if (v === "resources") loadResources();
3736
3756
  else if (v === "recall") loadRecall();
3757
+ else if (v === "codegraph") loadCodeGraph();
3737
3758
  else if (v === "packs") loadPacks();
3738
3759
  // The chart was sized while its view may have been hidden; nudge it on return.
3739
3760
  else if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
@@ -3857,6 +3878,90 @@ export {
3857
3878
  });
3858
3879
  }
3859
3880
 
3881
+ // --- code graph subpage (native structural code memory) ---
3882
+ let cgProject = null;
3883
+ // Render one architecture/schema field defensively (arrays -> chips, objects -> count).
3884
+ function cgItem(x) {
3885
+ if (x && typeof x === "object") return String(x.name || x.label || x.path || x.id || JSON.stringify(x)).slice(0, 60);
3886
+ return String(x);
3887
+ }
3888
+ function cgVal(v) {
3889
+ if (Array.isArray(v)) {
3890
+ if (!v.length) return '<span class="tag">none</span>';
3891
+ return v.slice(0, 16).map((x) => '<span class="tag">' + esc(cgItem(x)) + "</span>").join(" ")
3892
+ + (v.length > 16 ? ' <span class="set-hint">+' + (v.length - 16) + " more</span>" : "");
3893
+ }
3894
+ if (v && typeof v === "object") {
3895
+ const ents = Object.entries(v);
3896
+ if (!ents.length) return '<span class="tag">none</span>';
3897
+ return ents.slice(0, 16).map(([k, n]) => '<span class="tag">' + esc(k) + " " + esc(String(n)) + "</span>").join(" ");
3898
+ }
3899
+ return esc(String(v));
3900
+ }
3901
+ function cgSection(title, obj) {
3902
+ const entries = obj && typeof obj === "object" ? Object.entries(obj) : [];
3903
+ if (!entries.length) return "";
3904
+ return '<div class="panel"><div class="set-cat-h">' + esc(title) + "</div>"
3905
+ + 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("")
3906
+ + "</div>";
3907
+ }
3908
+ function renderCodeGraph(view) {
3909
+ view = view || {};
3910
+ const on = !!view.enabled;
3911
+ const t = $("cgToggle");
3912
+ t.classList.toggle("on", on); t.setAttribute("aria-checked", on ? "true" : "false"); t.textContent = on ? "On" : "Off";
3913
+ const projects = view.projects || [];
3914
+ const projHtml = projects.length
3915
+ ? projects.map((p) => {
3916
+ const sel = p.name === view.selected;
3917
+ return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(p.name)
3918
+ + (sel ? ' <span class="tag" style="color:var(--ok)">viewing</span>' : "")
3919
+ + '</div><div class="set-hint">' + esc((p.files || 0) + " files · " + (p.symbols || 0) + " symbols · " + (p.root || "")) + '</div></div>'
3920
+ + '<div class="set-ctl"><button type="button" class="toggle' + (sel ? " on" : "") + '" data-cg-project="' + esc(p.name) + '">View</button></div></div>';
3921
+ }).join("")
3922
+ : '<div class="empty" style="padding:12px 0">No projects indexed yet. Use "Index a project" above.</div>';
3923
+ let html = '<div class="panel"><div class="set-cat-h">Indexed projects <small>' + projects.length + "</small></div>" + projHtml + "</div>";
3924
+ if (view.architecture) html += cgSection("Architecture" + (view.selected ? " - " + view.selected : ""), view.architecture);
3925
+ if (view.schema) html += cgSection("Graph schema", view.schema);
3926
+ $("cgBody").innerHTML = html;
3927
+ }
3928
+ async function loadCodeGraph() {
3929
+ $("cgNote").textContent = "";
3930
+ try {
3931
+ const q = cgProject ? "?project=" + encodeURIComponent(cgProject) : "";
3932
+ renderCodeGraph(await (await fetch("/api/codegraph" + q, { cache: "no-store" })).json());
3933
+ } catch { $("cgBody").innerHTML = '<div class="empty" style="padding:12px 0">Code graph unavailable.</div>'; }
3934
+ }
3935
+ async function cgPost(payload, working) {
3936
+ setNote("cgNote", working);
3937
+ try {
3938
+ const out = await (await fetch("/api/codegraph", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) })).json();
3939
+ if (out.view) renderCodeGraph(out.view);
3940
+ return out;
3941
+ } catch { setNote("cgNote", "Could not reach the server.", "err"); return null; }
3942
+ }
3943
+ async function cgToggleAction() {
3944
+ const turnOn = $("cgToggle").getAttribute("aria-checked") !== "true";
3945
+ const out = await cgPost({ op: "toggle", on: turnOn, project: cgProject || undefined }, "Saving...");
3946
+ if (out) setNote("cgNote", out.ok ? (turnOn ? "Code graph enabled - tools exposed to your agents (restart them to load it)." : "Code graph disabled.") : (out.error || "Action failed."), out.ok ? "ok" : "err");
3947
+ }
3948
+ async function cgIndexAction() {
3949
+ const root = $("cgRoot").value.trim();
3950
+ const out = await cgPost({ op: "index", root: root || undefined }, "Indexing...");
3951
+ if (out) { if (out.view && out.view.selected) cgProject = out.view.selected; setNote("cgNote", out.ok ? "Indexed." : (out.error || "Indexing failed."), out.ok ? "ok" : "err"); }
3952
+ }
3953
+ function wireCodeGraph() {
3954
+ $("cgRefresh").addEventListener("click", loadCodeGraph);
3955
+ $("cgToggle").addEventListener("click", cgToggleAction);
3956
+ $("cgIndex").addEventListener("click", cgIndexAction);
3957
+ $("view-codegraph").addEventListener("click", (e) => {
3958
+ const b = e.target.closest("button[data-cg-project]");
3959
+ if (!b) return;
3960
+ cgProject = b.dataset.cgProject;
3961
+ loadCodeGraph();
3962
+ });
3963
+ }
3964
+
3860
3965
  // --- recall subpage (local session memory; reads your own transcripts) ---
3861
3966
  const RECALL_TYPES = ["bugfix", "feature", "refactor", "change", "discovery", "decision", "security"];
3862
3967
  let recallProjSig = "";
@@ -4425,6 +4530,7 @@ export {
4425
4530
  wireUsage();
4426
4531
  wireResources();
4427
4532
  wireRecall();
4533
+ wireCodeGraph();
4428
4534
  wirePacks();
4429
4535
  wireScope();
4430
4536
  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.69",
3
+ "version": "0.1.71",
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": [