@enigmax/dashboard 0.1.52 → 0.1.54
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 +73 -15
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -361,6 +361,13 @@
|
|
|
361
361
|
}
|
|
362
362
|
.modal-back.show .modal { transform: none; opacity: 1; }
|
|
363
363
|
.modal.wide { max-width: 900px; }
|
|
364
|
+
.as-top { display: flex; gap: 8px; margin: 14px 0 6px; }
|
|
365
|
+
.as-list { max-height: 50vh; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 4px 12px; }
|
|
366
|
+
.as-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--surface2); cursor: pointer; }
|
|
367
|
+
.as-row:last-child { border-bottom: none; }
|
|
368
|
+
.as-row input { accent-color: var(--accent); width: 15px; height: 15px; flex-shrink: 0; }
|
|
369
|
+
.as-name { font-size: 13px; color: var(--text); font-family: var(--mono); }
|
|
370
|
+
.as-src { margin-left: auto; display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
|
|
364
371
|
.modal-title { font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--text); }
|
|
365
372
|
.modal-msg { color: var(--muted); font-size: 13px; line-height: 1.5; margin-top: 8px; word-break: break-word; }
|
|
366
373
|
.modal-input {
|
|
@@ -632,6 +639,11 @@
|
|
|
632
639
|
<div class="set-cat-h">Skill settings</div>
|
|
633
640
|
<div id="skillSettings" class="set-grid"></div>
|
|
634
641
|
</div>
|
|
642
|
+
<div class="panel" id="memPanel">
|
|
643
|
+
<div class="set-cat-h">Agent memory</div>
|
|
644
|
+
<div class="set-cat-b">Customize the instruction files enigma deploys to each agent (CLAUDE.md / AGENTS.md). Your edits follow the Customized-skill updates policy on the next sync: overwrite restores the managed version, keep preserves them.</div>
|
|
645
|
+
<div id="memBody" class="set-grid"><div class="empty" style="padding:12px 0">Loading...</div></div>
|
|
646
|
+
</div>
|
|
635
647
|
<div class="panel">
|
|
636
648
|
<div id="skillsBody"><div class="empty" style="padding:24px 0">Loading skills...</div></div>
|
|
637
649
|
<div id="skillsNote" class="set-note"></div>
|
|
@@ -664,11 +676,6 @@
|
|
|
664
676
|
</div>
|
|
665
677
|
</div>
|
|
666
678
|
</div>
|
|
667
|
-
<div class="panel" id="memPanel">
|
|
668
|
-
<div class="set-cat-h">Agent memory</div>
|
|
669
|
-
<div class="set-cat-b">Customize the instruction files enigma deploys to each agent (CLAUDE.md / AGENTS.md). Your edits follow the Customized-skill updates policy on the next sync: overwrite restores the managed version, keep preserves them.</div>
|
|
670
|
-
<div id="memBody" class="set-grid"><div class="empty" style="padding:12px 0">Loading...</div></div>
|
|
671
|
-
</div>
|
|
672
679
|
</section>
|
|
673
680
|
|
|
674
681
|
<section id="view-resources" style="display:none">
|
|
@@ -2596,12 +2603,10 @@ export {
|
|
|
2596
2603
|
async function loadSettings() {
|
|
2597
2604
|
if (isProjectScope()) return loadProjectSettings();
|
|
2598
2605
|
const port = $("dashPortPanel"); if (port) port.style.display = ""; // global-only panel
|
|
2599
|
-
const mp = $("memPanel"); if (mp) mp.style.display = "";
|
|
2600
2606
|
try {
|
|
2601
2607
|
const data = await (await fetch("/api/settings", { cache: "no-store" })).json();
|
|
2602
2608
|
renderSettings(data.categories || []);
|
|
2603
2609
|
renderDashPort(data);
|
|
2604
|
-
loadMemory();
|
|
2605
2610
|
} catch { $("settingsBody").innerHTML = '<div class="empty" style="padding:24px 0">Settings unavailable.</div>'; }
|
|
2606
2611
|
}
|
|
2607
2612
|
|
|
@@ -2650,7 +2655,7 @@ export {
|
|
|
2650
2655
|
}
|
|
2651
2656
|
|
|
2652
2657
|
async function loadMemory(project) {
|
|
2653
|
-
const el =
|
|
2658
|
+
const el = $("memBody"); // global + project share this panel (one scope shown at a time)
|
|
2654
2659
|
if (!el) return;
|
|
2655
2660
|
try {
|
|
2656
2661
|
const q = project ? "/api/memory?path=" + encodeURIComponent(project) : "/api/memory";
|
|
@@ -2703,8 +2708,8 @@ export {
|
|
|
2703
2708
|
} catch { toast("Could not reach the server.", "err"); }
|
|
2704
2709
|
}
|
|
2705
2710
|
|
|
2706
|
-
//
|
|
2707
|
-
//
|
|
2711
|
+
// The memory editor lives in the Skills tab (#memBody, scope-aware via memProject());
|
|
2712
|
+
// a document-level delegate handles its edit/reset buttons in global and project scope.
|
|
2708
2713
|
document.addEventListener("click", (e) => {
|
|
2709
2714
|
const ed = e.target.closest && e.target.closest("[data-mem-edit]");
|
|
2710
2715
|
if (ed) { openMemoryEditor(ed.dataset.memEdit, memProject()); return; }
|
|
@@ -2952,7 +2957,7 @@ export {
|
|
|
2952
2957
|
$("skillsBody").innerHTML =
|
|
2953
2958
|
'<div class="panel"><div class="set-cat-h">Auto-detect skills by stack</div>'
|
|
2954
2959
|
+ '<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>'
|
|
2955
|
-
+ '<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
|
|
2960
|
+
+ '<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>'
|
|
2956
2961
|
+ section("Policy skills", "Deploy enigma's policy skills into this project, per app. A green chip means the skill is in that app's project folder.", skills, "No skills.");
|
|
2957
2962
|
return;
|
|
2958
2963
|
}
|
|
@@ -3069,6 +3074,7 @@ export {
|
|
|
3069
3074
|
if (isProjectScope()) return loadProjectSkills();
|
|
3070
3075
|
skillScope = "global";
|
|
3071
3076
|
loadSkillSettings();
|
|
3077
|
+
loadMemory();
|
|
3072
3078
|
try {
|
|
3073
3079
|
const res = await fetch("/api/skills", { cache: "no-store" });
|
|
3074
3080
|
renderSkills((await res.json()).skills || []);
|
|
@@ -3434,7 +3440,6 @@ export {
|
|
|
3434
3440
|
async function loadProjectSettings() {
|
|
3435
3441
|
const el = $("settingsBody");
|
|
3436
3442
|
const port = $("dashPortPanel"); if (port) port.style.display = "none"; // global-only
|
|
3437
|
-
const mp = $("memPanel"); if (mp) mp.style.display = "none"; // global panel; project memory is rendered inline below
|
|
3438
3443
|
el.innerHTML = '<div class="empty" style="padding:24px 0">Loading...</div>';
|
|
3439
3444
|
let d; try { d = await fetchProjectDetail(); } catch { el.innerHTML = '<div class="empty" style="padding:24px 0">Project unavailable.</div>'; return; }
|
|
3440
3445
|
if (d.error) { el.innerHTML = '<div class="empty" style="padding:24px 0">' + esc(d.error) + "</div>"; return; }
|
|
@@ -3449,13 +3454,11 @@ export {
|
|
|
3449
3454
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Git hooks</div><div class="set-hint">commit secret guard for this repo</div></div><div class="set-ctl">' + hooks + "</div></div>"
|
|
3450
3455
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">AI quality gate</div><div class="set-hint">validate changes through the gate pipeline before push (needs the gate feature enabled)</div></div><div class="set-ctl">' + gate + "</div></div></div></div>"
|
|
3451
3456
|
+ '<div class="panel"><div class="set-cat-h">Project config</div><div class="set-cat-b">applies only to this project (its .enigma.json). Settings not shown here are global-only - switch Scope to Global for those.</div><div class="set-grid">' + cfg + "</div></div>"
|
|
3452
|
-
+ '<div class="panel"><div class="set-cat-h">Agent memory</div><div class="set-cat-b">Customize this project\'s instruction files (CLAUDE.md / AGENTS.md). Edits follow the Customized-skill updates policy on the next sync.</div><div id="projMemBody" class="set-grid"><div class="empty" style="padding:12px 0">Loading...</div></div></div>'
|
|
3453
3457
|
+ '<div class="panel"><div class="set-cat-h">Manage</div><div class="set-grid">'
|
|
3454
3458
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Name</div><div class="set-hint">display name for this project (must be unique)</div></div><div class="set-ctl"><input class="linput" data-proj-name value="' + esc(d.label) + '" spellcheck="false" style="width:200px"></div></div>'
|
|
3455
3459
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Description</div><div class="set-hint">optional note shown in the header</div></div><div class="set-ctl ladd" style="align-items:center"><input class="linput" data-proj-desc value="' + esc(d.description || "") + '" placeholder="optional" style="flex:none;width:240px"><button type="button" class="toggle on" data-proj-save-meta>Save</button></div></div>'
|
|
3456
3460
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Forget project</div><div class="set-hint">remove it from the dashboard list - its files are not touched</div></div><div class="set-ctl"><button type="button" class="toggle" data-proj-forget>Forget</button></div></div>'
|
|
3457
3461
|
+ "</div></div>";
|
|
3458
|
-
loadMemory(currentScope);
|
|
3459
3462
|
}
|
|
3460
3463
|
async function saveProjectMeta() {
|
|
3461
3464
|
if (!isProjectScope()) return;
|
|
@@ -3474,6 +3477,7 @@ export {
|
|
|
3474
3477
|
skillScope = "project";
|
|
3475
3478
|
const el = $("skillsBody");
|
|
3476
3479
|
const ss = $("skillSettings"); if (ss) ss.innerHTML = '<div class="set-cat-b" style="padding:0 0 4px">Skill settings are global - switch Scope to Global to change them.</div>';
|
|
3480
|
+
loadMemory(currentScope);
|
|
3477
3481
|
el.innerHTML = '<div class="empty" style="padding:24px 0">Loading...</div>';
|
|
3478
3482
|
let d; try { d = await fetchProjectDetail(); } catch { el.innerHTML = '<div class="empty" style="padding:24px 0">Project unavailable.</div>'; return; }
|
|
3479
3483
|
if (d.error) { el.innerHTML = '<div class="empty" style="padding:24px 0">' + esc(d.error) + "</div>"; return; }
|
|
@@ -3498,6 +3502,60 @@ export {
|
|
|
3498
3502
|
loadProjectsList(); // refresh selector badges
|
|
3499
3503
|
} catch { setNote(activeView === "skills" ? "skillsNote" : "settingsNote", "Could not reach the server.", "err"); }
|
|
3500
3504
|
}
|
|
3505
|
+
// Autoskills: detect the project's stack, let the user pick which community skills to
|
|
3506
|
+
// install (all checked by default), then install the selected ones - or cancel.
|
|
3507
|
+
async function autoskillsFlow() {
|
|
3508
|
+
if (!isProjectScope()) return;
|
|
3509
|
+
setNote("skillsNote", "Detecting your stack...");
|
|
3510
|
+
let out;
|
|
3511
|
+
try { out = await (await fetch("/api/projects/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: currentScope, op: "autoskills-detect" }) })).json(); }
|
|
3512
|
+
catch { setNote("skillsNote", "Could not reach the server.", "err"); return; }
|
|
3513
|
+
if (!out.ok) { setNote("skillsNote", out.error || "Detection failed.", "err"); return; }
|
|
3514
|
+
const skills = out.skills || [];
|
|
3515
|
+
if (!skills.length) { setNote("skillsNote", (out.detected ? "Detected " + out.detected + " - " : "") + "no matching community skills.", "ok"); return; }
|
|
3516
|
+
setNote("skillsNote", out.detected ? ("Detected " + out.detected + ".") : "");
|
|
3517
|
+
const selected = await openAutoskillsModal(out.detected, skills);
|
|
3518
|
+
if (selected == null) { setNote("skillsNote", "Cancelled."); return; }
|
|
3519
|
+
if (!selected.length) { setNote("skillsNote", "Nothing selected.", "err"); return; }
|
|
3520
|
+
setNote("skillsNote", "Installing " + selected.length + " skill(s)...");
|
|
3521
|
+
try {
|
|
3522
|
+
const ins = await (await fetch("/api/projects/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: currentScope, op: "autoskills-install", skills: selected }) })).json();
|
|
3523
|
+
if (ins.ok) { setNote("skillsNote", ins.note || "Installed.", "ok"); loadProjectSkills(); loadProjectsList(); }
|
|
3524
|
+
else setNote("skillsNote", ins.error || ins.note || "Install failed.", "err");
|
|
3525
|
+
} catch { setNote("skillsNote", "Could not reach the server.", "err"); }
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
// Selection modal: one checkbox per detected skill (all checked), Select all / Clear, and
|
|
3529
|
+
// Install selected / Cancel. Resolves to the chosen refs, or null when cancelled.
|
|
3530
|
+
function openAutoskillsModal(detected, skills) {
|
|
3531
|
+
return new Promise((resolve) => {
|
|
3532
|
+
const back = document.createElement("div");
|
|
3533
|
+
back.className = "modal-back";
|
|
3534
|
+
const rows = skills.map((s, i) =>
|
|
3535
|
+
'<label class="as-row"><input type="checkbox" checked data-as="' + i + '">'
|
|
3536
|
+
+ '<span class="as-name">' + esc(s.name) + "</span>"
|
|
3537
|
+
+ '<span class="as-src">' + (s.sources || []).map((x) => '<span class="tag">' + esc(x) + "</span>").join("") + "</span></label>").join("");
|
|
3538
|
+
back.innerHTML = '<div class="modal wide" role="dialog" aria-modal="true">'
|
|
3539
|
+
+ '<div class="modal-title">Install stack skills</div>'
|
|
3540
|
+
+ '<div class="modal-msg">' + (detected ? "Detected " + esc(detected) + ". " : "") + esc(skills.length + " matching community skill(s)") + " - pick which to install. They are hash-verified and kept separate from the policy skills.</div>"
|
|
3541
|
+
+ '<div class="as-top"><button type="button" class="toggle" data-as-all>Select all</button><button type="button" class="toggle" data-as-none>Clear</button></div>'
|
|
3542
|
+
+ '<div class="as-list">' + rows + "</div>"
|
|
3543
|
+
+ '<div class="modal-acts"><button type="button" class="toggle modal-cancel">Cancel</button>'
|
|
3544
|
+
+ '<button type="button" class="toggle on modal-ok">Install selected</button></div></div>';
|
|
3545
|
+
document.body.appendChild(back);
|
|
3546
|
+
requestAnimationFrame(() => back.classList.add("show"));
|
|
3547
|
+
let done = false;
|
|
3548
|
+
const close = (val) => { if (done) return; done = true; back.classList.remove("show"); setTimeout(() => back.remove(), 200); resolve(val); };
|
|
3549
|
+
const boxes = () => Array.from(back.querySelectorAll("input[data-as]"));
|
|
3550
|
+
back.querySelector("[data-as-all]").addEventListener("click", () => boxes().forEach((b) => { b.checked = true; }));
|
|
3551
|
+
back.querySelector("[data-as-none]").addEventListener("click", () => boxes().forEach((b) => { b.checked = false; }));
|
|
3552
|
+
back.querySelector(".modal-cancel").addEventListener("click", () => close(null));
|
|
3553
|
+
back.addEventListener("mousedown", (e) => { if (e.target === back) close(null); });
|
|
3554
|
+
back.addEventListener("keydown", (e) => { if (e.key === "Escape") { e.preventDefault(); close(null); } });
|
|
3555
|
+
back.querySelector(".modal-ok").addEventListener("click", () => close(boxes().filter((b) => b.checked).map((b) => skills[Number(b.dataset.as)].ref)));
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3501
3559
|
async function forgetProject() {
|
|
3502
3560
|
if (!isProjectScope()) return;
|
|
3503
3561
|
if (!(await askConfirm("Forget project", "Stop managing " + scopeLabel() + "? Its files are not touched.", false))) return;
|
|
@@ -3509,7 +3567,7 @@ export {
|
|
|
3509
3567
|
}
|
|
3510
3568
|
function onProjectControlClick(e) {
|
|
3511
3569
|
if (!isProjectScope()) return;
|
|
3512
|
-
if (e.target.closest("[data-proj-autoskills]")) {
|
|
3570
|
+
if (e.target.closest("[data-proj-autoskills]")) { autoskillsFlow(); return; }
|
|
3513
3571
|
const chip = e.target.closest("[data-pskill]"); // per-app deploy chip in the Skills view
|
|
3514
3572
|
if (chip) { projAction({ op: "skill", name: chip.dataset.pskill, agent: chip.dataset.pagent, on: chip.dataset.poff !== "1" }); return; }
|
|
3515
3573
|
const sk = e.target.closest("[data-proj-skill]");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
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": [
|