@enigmax/dashboard 0.1.53 → 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.
Files changed (2) hide show
  1. package/assets/index.html +63 -2
  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 {
@@ -2950,7 +2957,7 @@ export {
2950
2957
  $("skillsBody").innerHTML =
2951
2958
  '<div class="panel"><div class="set-cat-h">Auto-detect skills by stack</div>'
2952
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>'
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>'
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>'
2954
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.");
2955
2962
  return;
2956
2963
  }
@@ -3495,6 +3502,60 @@ export {
3495
3502
  loadProjectsList(); // refresh selector badges
3496
3503
  } catch { setNote(activeView === "skills" ? "skillsNote" : "settingsNote", "Could not reach the server.", "err"); }
3497
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
+
3498
3559
  async function forgetProject() {
3499
3560
  if (!isProjectScope()) return;
3500
3561
  if (!(await askConfirm("Forget project", "Stop managing " + scopeLabel() + "? Its files are not touched.", false))) return;
@@ -3506,7 +3567,7 @@ export {
3506
3567
  }
3507
3568
  function onProjectControlClick(e) {
3508
3569
  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; }
3570
+ if (e.target.closest("[data-proj-autoskills]")) { autoskillsFlow(); return; }
3510
3571
  const chip = e.target.closest("[data-pskill]"); // per-app deploy chip in the Skills view
3511
3572
  if (chip) { projAction({ op: "skill", name: chip.dataset.pskill, agent: chip.dataset.pagent, on: chip.dataset.poff !== "1" }); return; }
3512
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.53",
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": [