@enigmax/dashboard 0.1.1 → 0.1.3
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 +185 -3
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -108,6 +108,29 @@
|
|
|
108
108
|
border-radius: 7px; padding: 5px 10px; font-size: 12px; font-family: inherit; cursor: pointer;
|
|
109
109
|
}
|
|
110
110
|
.set-note { color: var(--accent); font-size: 12px; min-height: 16px; margin-top: 10px; }
|
|
111
|
+
.tag {
|
|
112
|
+
display: inline-block; font-size: 11px; color: var(--muted); background: var(--surface2);
|
|
113
|
+
border: 1px solid var(--border); border-radius: 6px; padding: 1px 7px; margin-left: 6px; vertical-align: middle;
|
|
114
|
+
}
|
|
115
|
+
.tag.ext { color: var(--accent2); }
|
|
116
|
+
.tag.warn { color: var(--accent); border-color: var(--accent); }
|
|
117
|
+
.editor {
|
|
118
|
+
width: 100%; min-height: 360px; resize: vertical; background: var(--surface2); color: var(--text);
|
|
119
|
+
border: 1px solid var(--border); border-radius: 8px; padding: 12px;
|
|
120
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;
|
|
121
|
+
}
|
|
122
|
+
.btn-danger {
|
|
123
|
+
background: var(--surface2); color: var(--accent2); border: 1px solid var(--border);
|
|
124
|
+
border-radius: 7px; padding: 5px 14px; font-size: 12px; cursor: pointer; font-family: inherit;
|
|
125
|
+
}
|
|
126
|
+
.btn-danger:hover { border-color: var(--accent2); }
|
|
127
|
+
.sys-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0 24px; }
|
|
128
|
+
.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; }
|
|
129
|
+
.sys-item .k { color: var(--muted); }
|
|
130
|
+
.pill { font-size: 11px; border-radius: 6px; padding: 2px 9px; border: 1px solid var(--border); white-space: nowrap; }
|
|
131
|
+
.pill.on { color: #06210f; background: var(--good); border-color: var(--good); font-weight: 600; }
|
|
132
|
+
.pill.off { color: var(--muted); background: var(--surface2); }
|
|
133
|
+
.pill.lvl { color: #1a1206; background: var(--accent); border-color: var(--accent); font-weight: 600; }
|
|
111
134
|
@media (max-width: 720px) { .grid { grid-template-columns: repeat(2, 1fr); } header { gap: 12px; } }
|
|
112
135
|
</style>
|
|
113
136
|
</head>
|
|
@@ -119,6 +142,7 @@
|
|
|
119
142
|
</div>
|
|
120
143
|
<nav class="tabs">
|
|
121
144
|
<a href="#/" data-view="savings" class="tab active">Savings</a>
|
|
145
|
+
<a href="#/skills" data-view="skills" class="tab">Skills</a>
|
|
122
146
|
<a href="#/settings" data-view="settings" class="tab">Settings</a>
|
|
123
147
|
</nav>
|
|
124
148
|
<div class="nav">
|
|
@@ -148,6 +172,12 @@
|
|
|
148
172
|
<div class="card"><div class="label">Best Compression</div><div id="best" class="value">-</div><div class="foot">Most saved in one call</div></div>
|
|
149
173
|
</div>
|
|
150
174
|
|
|
175
|
+
<div class="panel">
|
|
176
|
+
<h2 style="margin-bottom:4px">Enigma Systems <small>what's active - configuration, not savings</small></h2>
|
|
177
|
+
<div class="sub" style="margin-bottom:14px">Only Context Compression and (opt-in) real tool-usage are measured for savings below. The rest is shown as state: enigma can't measure their effect because they run inside the agent, not through enigma - so it reports their configuration honestly instead of inventing a number.</div>
|
|
178
|
+
<div id="systems" class="sys-grid"><div class="empty" style="padding:16px 0">Loading...</div></div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
151
181
|
<div class="panel">
|
|
152
182
|
<div class="panel-head">
|
|
153
183
|
<h2>Savings Per Day <small id="rangeLabel"></small></h2>
|
|
@@ -233,6 +263,28 @@
|
|
|
233
263
|
</div>
|
|
234
264
|
</main>
|
|
235
265
|
|
|
266
|
+
<section id="view-skills" style="display:none">
|
|
267
|
+
<div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
|
|
268
|
+
<div style="flex:1">
|
|
269
|
+
<h1>Skills</h1>
|
|
270
|
+
<p>The skills installed in your agents - enigma's own and any external ones you added. Edit a skill's content, disable/enable or remove it, and check whether enigma's skills are up to date.</p>
|
|
271
|
+
</div>
|
|
272
|
+
<button id="skillsCheck" type="button" class="toggle on" style="white-space:nowrap">Check for updates</button>
|
|
273
|
+
</div>
|
|
274
|
+
<div id="skillEditor" class="panel" style="display:none">
|
|
275
|
+
<div class="panel-head"><h2>Editing <span id="seName"></span> <small id="seWarn"></small></h2></div>
|
|
276
|
+
<textarea id="seText" class="editor" spellcheck="false"></textarea>
|
|
277
|
+
<div style="margin-top:10px;display:flex;gap:8px">
|
|
278
|
+
<button id="seSave" type="button" class="toggle on">Save</button>
|
|
279
|
+
<button id="seCancel" type="button" class="toggle">Cancel</button>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="panel">
|
|
283
|
+
<div id="skillsBody"><div class="empty" style="padding:24px 0">Loading skills...</div></div>
|
|
284
|
+
<div id="skillsNote" class="set-note"></div>
|
|
285
|
+
</div>
|
|
286
|
+
</section>
|
|
287
|
+
|
|
236
288
|
<section id="view-settings" style="display:none">
|
|
237
289
|
<div class="page-head">
|
|
238
290
|
<h1>Settings</h1>
|
|
@@ -656,15 +708,144 @@
|
|
|
656
708
|
} catch { $("settingsBody").innerHTML = '<div class="empty" style="padding:24px 0">Settings unavailable.</div>'; }
|
|
657
709
|
}
|
|
658
710
|
|
|
659
|
-
// ---
|
|
711
|
+
// --- "Enigma systems" overview (factual state, never invented savings) ---
|
|
712
|
+
function pill(state, text) { return '<span class="pill ' + state + '">' + esc(text) + "</span>"; }
|
|
713
|
+
function boolPill(b) { return b ? pill("on", "on") : pill("off", "off"); }
|
|
714
|
+
function levelPill(v) { return (!v || v === "off") ? pill("off", "off") : pill("lvl", v); }
|
|
715
|
+
|
|
716
|
+
function renderSystems(s) {
|
|
717
|
+
const el = $("systems");
|
|
718
|
+
if (!s) { el.innerHTML = '<div class="empty" style="padding:16px 0">Status unavailable.</div>'; return; }
|
|
719
|
+
const item = (k, v) => '<div class="sys-item"><span class="k">' + k + '</span><span class="v">' + v + "</span></div>";
|
|
720
|
+
const sk = s.skills || {};
|
|
721
|
+
const skillTags = '<span class="tag">' + (sk.enigma || 0) + " enigma</span>"
|
|
722
|
+
+ '<span class="tag ext">' + (sk.external || 0) + " external</span>"
|
|
723
|
+
+ ((sk.disabled || 0) ? '<span class="tag">' + sk.disabled + " disabled</span>" : "");
|
|
724
|
+
el.innerHTML =
|
|
725
|
+
item("Context compression (MCP) · measured", boolPill(s.compress))
|
|
726
|
+
+ item("Real tool-usage stats · measured", boolPill(s.usageStats))
|
|
727
|
+
+ item("Token-efficient output", levelPill(s.outputStyle))
|
|
728
|
+
+ item("Minimal code", levelPill(s.minimalCode))
|
|
729
|
+
+ item("Parallel sub-agents", boolPill(s.parallelSubagents))
|
|
730
|
+
+ item("Auto-lint on edit", boolPill(s.autoLint))
|
|
731
|
+
+ item("Commit emoji", boolPill(s.commitEmoji))
|
|
732
|
+
+ item("Local dashboard", pill(s.dashboard === "off" ? "off" : "lvl", s.dashboard || "off"))
|
|
733
|
+
+ item("Skills", skillTags);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async function loadSystems() {
|
|
737
|
+
try {
|
|
738
|
+
const r = await fetch("/api/status", { cache: "no-store" });
|
|
739
|
+
renderSystems((await r.json()).systems);
|
|
740
|
+
} catch { $("systems").innerHTML = '<div class="empty" style="padding:16px 0">Status unavailable.</div>'; }
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// --- skills subpage (lists enigma + external skills over /api/skills) ---
|
|
744
|
+
function skillRow(s) {
|
|
745
|
+
const ver = s.version ? '<span class="tag">v' + esc(s.version) + "</span>" : "";
|
|
746
|
+
const where = s.agents && s.agents.length ? '<span class="tag">' + s.agents.map(esc).join(", ") + "</span>"
|
|
747
|
+
: (s.discarded ? '<span class="tag">disabled</span>' : '<span class="tag">not deployed</span>');
|
|
748
|
+
const ext = s.source === "external" ? '<span class="tag ext">external</span>' : "";
|
|
749
|
+
const upd = s.source !== "enigma" ? ""
|
|
750
|
+
: s.update === "update" ? '<span class="tag warn">update available</span>'
|
|
751
|
+
: s.update === "modified" ? '<span class="tag ext">modified</span>' : "";
|
|
752
|
+
const edit = '<button type="button" class="toggle" data-name="' + esc(s.name) + '" data-action="edit" data-enigma="' + (s.source === "enigma" ? 1 : 0) + '">Edit</button>';
|
|
753
|
+
const main = s.source === "enigma"
|
|
754
|
+
? '<button type="button" class="toggle' + (s.discarded ? "" : " on") + '" data-name="' + esc(s.name)
|
|
755
|
+
+ '" data-action="' + (s.discarded ? "enable" : "disable") + '">' + (s.discarded ? "Disabled" : "Enabled") + "</button>"
|
|
756
|
+
: '<button type="button" class="btn-danger" data-name="' + esc(s.name) + '" data-action="remove">Remove</button>';
|
|
757
|
+
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + upd + where + "</div>"
|
|
758
|
+
+ '<div class="set-hint">' + esc(s.description || "") + '</div></div><div class="set-ctl" style="display:flex;gap:8px">' + edit + main + "</div></div>";
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function renderSkills(skills) {
|
|
762
|
+
const enigma = skills.filter((s) => s.source === "enigma");
|
|
763
|
+
const ext = skills.filter((s) => s.source === "external");
|
|
764
|
+
const section = (title, blurb, list, emptyText) => '<div class="set-cat"><div class="set-cat-h">' + title + "</div>"
|
|
765
|
+
+ '<div class="set-cat-b">' + blurb + "</div>"
|
|
766
|
+
+ (list.length ? list.map(skillRow).join("") : '<div class="empty" style="padding:16px 0">' + emptyText + "</div>") + "</div>";
|
|
767
|
+
$("skillsBody").innerHTML =
|
|
768
|
+
section("Enigma skills", "Distributed and kept current by enigma. Disable to remove from your agents; enable to restore.", enigma, "No enigma skills.")
|
|
769
|
+
+ 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.");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async function postSkill(name, action) {
|
|
773
|
+
$("skillsNote").textContent = "Working...";
|
|
774
|
+
try {
|
|
775
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action }) });
|
|
776
|
+
const out = await res.json();
|
|
777
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not " + action + " " + name + ": " + (out.error || "error"); return; }
|
|
778
|
+
if (out.skills) renderSkills(out.skills);
|
|
779
|
+
$("skillsNote").textContent = out.note || ("Done: " + name);
|
|
780
|
+
} catch { $("skillsNote").textContent = "Could not reach the server to change " + name + "."; }
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Inline SKILL.md editor (one skill at a time).
|
|
784
|
+
let editing = null;
|
|
785
|
+
async function openEditor(name, isEnigma) {
|
|
786
|
+
$("skillsNote").textContent = "Loading " + name + "...";
|
|
787
|
+
try {
|
|
788
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action: "read" }) });
|
|
789
|
+
const out = await res.json();
|
|
790
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not open " + name + ": " + (out.error || "error"); return; }
|
|
791
|
+
editing = name;
|
|
792
|
+
$("seName").textContent = name;
|
|
793
|
+
$("seWarn").textContent = isEnigma ? "managed by enigma - local edits revert on the next update/sync" : "external skill";
|
|
794
|
+
$("seText").value = out.content || "";
|
|
795
|
+
$("skillEditor").style.display = "";
|
|
796
|
+
$("skillsNote").textContent = "";
|
|
797
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
798
|
+
$("seText").focus();
|
|
799
|
+
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
800
|
+
}
|
|
801
|
+
function closeEditor() { editing = null; $("skillEditor").style.display = "none"; }
|
|
802
|
+
async function saveEditor() {
|
|
803
|
+
if (!editing) return;
|
|
804
|
+
$("skillsNote").textContent = "Saving " + editing + "...";
|
|
805
|
+
try {
|
|
806
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editing, action: "save", content: $("seText").value }) });
|
|
807
|
+
const out = await res.json();
|
|
808
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not save " + editing + ": " + (out.error || "error"); return; }
|
|
809
|
+
if (out.skills) renderSkills(out.skills);
|
|
810
|
+
$("skillsNote").textContent = out.note || "Saved.";
|
|
811
|
+
closeEditor();
|
|
812
|
+
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function wireSkills() {
|
|
816
|
+
$("skillsBody").addEventListener("click", (e) => {
|
|
817
|
+
const b = e.target.closest("button[data-action]");
|
|
818
|
+
if (!b) return;
|
|
819
|
+
const { name, action } = b.dataset;
|
|
820
|
+
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
821
|
+
if (action === "remove" && !confirm('Remove the external skill "' + name + '" from all agents? This deletes it from disk.')) return;
|
|
822
|
+
postSkill(name, action);
|
|
823
|
+
});
|
|
824
|
+
$("seSave").addEventListener("click", saveEditor);
|
|
825
|
+
$("seCancel").addEventListener("click", closeEditor);
|
|
826
|
+
$("skillsCheck").addEventListener("click", () => postSkill("*", "check-updates"));
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
let skillsLoaded = false;
|
|
830
|
+
async function loadSkills() {
|
|
831
|
+
try {
|
|
832
|
+
const res = await fetch("/api/skills", { cache: "no-store" });
|
|
833
|
+
renderSkills((await res.json()).skills || []);
|
|
834
|
+
} catch { $("skillsBody").innerHTML = '<div class="empty" style="padding:24px 0">Skills unavailable.</div>'; }
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// --- hash routing between the Savings, Skills and Settings subpages ---
|
|
660
838
|
function route() {
|
|
661
|
-
const
|
|
839
|
+
const h = (location.hash || "").replace(/^#\/?/, "");
|
|
840
|
+
const v = (h === "settings" || h === "skills") ? h : "savings";
|
|
662
841
|
$("view-savings").style.display = v === "savings" ? "" : "none";
|
|
842
|
+
$("view-skills").style.display = v === "skills" ? "" : "none";
|
|
663
843
|
$("view-settings").style.display = v === "settings" ? "" : "none";
|
|
664
844
|
document.querySelectorAll(".tab").forEach((t) => t.classList.toggle("active", t.dataset.view === v));
|
|
665
845
|
if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
|
|
846
|
+
if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
|
|
666
847
|
// The chart was sized while its view may have been hidden; nudge it on return.
|
|
667
|
-
if (v === "savings"
|
|
848
|
+
if (v === "savings") { loadSystems(); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
|
|
668
849
|
}
|
|
669
850
|
|
|
670
851
|
initChart();
|
|
@@ -672,6 +853,7 @@
|
|
|
672
853
|
setInterval(poll, POLL_MS);
|
|
673
854
|
document.addEventListener("visibilitychange", () => { if (!document.hidden) poll(); });
|
|
674
855
|
wireSettings();
|
|
856
|
+
wireSkills();
|
|
675
857
|
window.addEventListener("hashchange", route);
|
|
676
858
|
route();
|
|
677
859
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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": [
|