@enigmax/dashboard 0.1.53 → 0.1.55

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 +68 -3
  2. 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 {
@@ -2637,7 +2644,11 @@ export {
2637
2644
  }
2638
2645
 
2639
2646
  function memoryRow(g) {
2640
- const apps = (g.agents || []).map((a) => '<span class="tag">' + esc(a.label) + "</span>").join(" ");
2647
+ // Apps are shown as brand logos (see AGENT_ICONS), never text - the label is the tooltip.
2648
+ const apps = '<span class="aicons">' + (g.agents || []).map((a) => {
2649
+ const ic = agentSvg(a.name);
2650
+ return ic ? '<span class="aicon" title="' + esc(a.label) + '" aria-label="' + esc(a.label) + '">' + ic + "</span>" : esc(a.label);
2651
+ }).join("") + "</span>";
2641
2652
  const state = !g.deployed ? '<span class="tag">not deployed</span>'
2642
2653
  : g.edited ? '<span class="tag exp">edited</span>'
2643
2654
  : g.managed ? '<span class="tag meas">managed</span>' : '<span class="tag">custom</span>';
@@ -2950,7 +2961,7 @@ export {
2950
2961
  $("skillsBody").innerHTML =
2951
2962
  '<div class="panel"><div class="set-cat-h">Auto-detect skills by stack</div>'
2952
2963
  + '<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>'
2953
- + '<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 &amp; install</button></div></div>'
2964
+ + '<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>'
2954
2965
  + 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.");
2955
2966
  return;
2956
2967
  }
@@ -3495,6 +3506,60 @@ export {
3495
3506
  loadProjectsList(); // refresh selector badges
3496
3507
  } catch { setNote(activeView === "skills" ? "skillsNote" : "settingsNote", "Could not reach the server.", "err"); }
3497
3508
  }
3509
+ // Autoskills: detect the project's stack, let the user pick which community skills to
3510
+ // install (all checked by default), then install the selected ones - or cancel.
3511
+ async function autoskillsFlow() {
3512
+ if (!isProjectScope()) return;
3513
+ setNote("skillsNote", "Detecting your stack...");
3514
+ let out;
3515
+ try { out = await (await fetch("/api/projects/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: currentScope, op: "autoskills-detect" }) })).json(); }
3516
+ catch { setNote("skillsNote", "Could not reach the server.", "err"); return; }
3517
+ if (!out.ok) { setNote("skillsNote", out.error || "Detection failed.", "err"); return; }
3518
+ const skills = out.skills || [];
3519
+ if (!skills.length) { setNote("skillsNote", (out.detected ? "Detected " + out.detected + " - " : "") + "no matching community skills.", "ok"); return; }
3520
+ setNote("skillsNote", out.detected ? ("Detected " + out.detected + ".") : "");
3521
+ const selected = await openAutoskillsModal(out.detected, skills);
3522
+ if (selected == null) { setNote("skillsNote", "Cancelled."); return; }
3523
+ if (!selected.length) { setNote("skillsNote", "Nothing selected.", "err"); return; }
3524
+ setNote("skillsNote", "Installing " + selected.length + " skill(s)...");
3525
+ try {
3526
+ 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();
3527
+ if (ins.ok) { setNote("skillsNote", ins.note || "Installed.", "ok"); loadProjectSkills(); loadProjectsList(); }
3528
+ else setNote("skillsNote", ins.error || ins.note || "Install failed.", "err");
3529
+ } catch { setNote("skillsNote", "Could not reach the server.", "err"); }
3530
+ }
3531
+
3532
+ // Selection modal: one checkbox per detected skill (all checked), Select all / Clear, and
3533
+ // Install selected / Cancel. Resolves to the chosen refs, or null when cancelled.
3534
+ function openAutoskillsModal(detected, skills) {
3535
+ return new Promise((resolve) => {
3536
+ const back = document.createElement("div");
3537
+ back.className = "modal-back";
3538
+ const rows = skills.map((s, i) =>
3539
+ '<label class="as-row"><input type="checkbox" checked data-as="' + i + '">'
3540
+ + '<span class="as-name">' + esc(s.name) + "</span>"
3541
+ + '<span class="as-src">' + (s.sources || []).map((x) => '<span class="tag">' + esc(x) + "</span>").join("") + "</span></label>").join("");
3542
+ back.innerHTML = '<div class="modal wide" role="dialog" aria-modal="true">'
3543
+ + '<div class="modal-title">Install stack skills</div>'
3544
+ + '<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>"
3545
+ + '<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>'
3546
+ + '<div class="as-list">' + rows + "</div>"
3547
+ + '<div class="modal-acts"><button type="button" class="toggle modal-cancel">Cancel</button>'
3548
+ + '<button type="button" class="toggle on modal-ok">Install selected</button></div></div>';
3549
+ document.body.appendChild(back);
3550
+ requestAnimationFrame(() => back.classList.add("show"));
3551
+ let done = false;
3552
+ const close = (val) => { if (done) return; done = true; back.classList.remove("show"); setTimeout(() => back.remove(), 200); resolve(val); };
3553
+ const boxes = () => Array.from(back.querySelectorAll("input[data-as]"));
3554
+ back.querySelector("[data-as-all]").addEventListener("click", () => boxes().forEach((b) => { b.checked = true; }));
3555
+ back.querySelector("[data-as-none]").addEventListener("click", () => boxes().forEach((b) => { b.checked = false; }));
3556
+ back.querySelector(".modal-cancel").addEventListener("click", () => close(null));
3557
+ back.addEventListener("mousedown", (e) => { if (e.target === back) close(null); });
3558
+ back.addEventListener("keydown", (e) => { if (e.key === "Escape") { e.preventDefault(); close(null); } });
3559
+ back.querySelector(".modal-ok").addEventListener("click", () => close(boxes().filter((b) => b.checked).map((b) => skills[Number(b.dataset.as)].ref)));
3560
+ });
3561
+ }
3562
+
3498
3563
  async function forgetProject() {
3499
3564
  if (!isProjectScope()) return;
3500
3565
  if (!(await askConfirm("Forget project", "Stop managing " + scopeLabel() + "? Its files are not touched.", false))) return;
@@ -3506,7 +3571,7 @@ export {
3506
3571
  }
3507
3572
  function onProjectControlClick(e) {
3508
3573
  if (!isProjectScope()) return;
3509
- if (e.target.closest("[data-proj-autoskills]")) { projAction({ op: "autoskills" }, "Scan this project and install the matching community skills for its stack?"); return; }
3574
+ if (e.target.closest("[data-proj-autoskills]")) { autoskillsFlow(); return; }
3510
3575
  const chip = e.target.closest("[data-pskill]"); // per-app deploy chip in the Skills view
3511
3576
  if (chip) { projAction({ op: "skill", name: chip.dataset.pskill, agent: chip.dataset.pagent, on: chip.dataset.poff !== "1" }); return; }
3512
3577
  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.53",
3
+ "version": "0.1.55",
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": [