@enigmax/dashboard 0.1.31 → 0.1.32
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 +72 -8
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -214,6 +214,13 @@
|
|
|
214
214
|
border: 1px solid var(--border); border-radius: 8px; padding: 12px;
|
|
215
215
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;
|
|
216
216
|
}
|
|
217
|
+
/* Per-app save targets in the skill editor. */
|
|
218
|
+
.se-agents { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 10px; }
|
|
219
|
+
.se-agents:empty { display: none; }
|
|
220
|
+
.se-ag-label { font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
221
|
+
.se-ag { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text); cursor: pointer; }
|
|
222
|
+
.se-ag input { accent-color: var(--good); }
|
|
223
|
+
.se-ag .aicon { color: var(--muted); }
|
|
217
224
|
.btn-danger {
|
|
218
225
|
background: var(--surface2); color: var(--accent2); border: 1px solid var(--border);
|
|
219
226
|
border-radius: 7px; padding: 5px 14px; font-size: 12px; cursor: pointer; font-family: inherit;
|
|
@@ -527,11 +534,16 @@
|
|
|
527
534
|
<div id="skillEditor" class="panel" style="display:none">
|
|
528
535
|
<div class="panel-head"><h2>Editing <span id="seName"></span> <small id="seWarn"></small></h2></div>
|
|
529
536
|
<textarea id="seText" class="editor" spellcheck="false"></textarea>
|
|
537
|
+
<div id="seAgents" class="se-agents" data-has="0"></div>
|
|
530
538
|
<div style="margin-top:10px;display:flex;gap:8px">
|
|
531
539
|
<button id="seSave" type="button" class="toggle on">Save</button>
|
|
532
540
|
<button id="seCancel" type="button" class="toggle">Cancel</button>
|
|
533
541
|
</div>
|
|
534
542
|
</div>
|
|
543
|
+
<div class="panel">
|
|
544
|
+
<div class="set-cat-h">Skill settings</div>
|
|
545
|
+
<div id="skillSettings" class="set-grid"></div>
|
|
546
|
+
</div>
|
|
535
547
|
<div class="panel">
|
|
536
548
|
<div id="skillsBody"><div class="empty" style="padding:24px 0">Loading skills...</div></div>
|
|
537
549
|
<div id="skillsNote" class="set-note"></div>
|
|
@@ -2157,17 +2169,18 @@ export {
|
|
|
2157
2169
|
+ '<div class="set-grid">' + c.settings.map(settingRow).join("") + "</div></div>").join("");
|
|
2158
2170
|
}
|
|
2159
2171
|
|
|
2160
|
-
async function postSetting(key, value, ctl) {
|
|
2161
|
-
|
|
2172
|
+
async function postSetting(key, value, ctl, noteEl) {
|
|
2173
|
+
const note = noteEl || $("settingsNote");
|
|
2174
|
+
note.textContent = "Saving " + key + "...";
|
|
2162
2175
|
try {
|
|
2163
2176
|
const res = await fetch("/api/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value }) });
|
|
2164
2177
|
const out = await res.json();
|
|
2165
|
-
if (!out.ok) {
|
|
2178
|
+
if (!out.ok) { note.textContent = "Could not change " + key + ": " + (out.error || "error"); return; }
|
|
2166
2179
|
const s = out.setting;
|
|
2167
2180
|
if (s && ctl.dataset.kind === "bool") { ctl.classList.toggle("on", s.value); ctl.textContent = s.value ? "On" : "Off"; ctl.dataset.on = s.value ? 1 : 0; }
|
|
2168
2181
|
else if (s) { ctl.value = s.choice; }
|
|
2169
|
-
|
|
2170
|
-
} catch {
|
|
2182
|
+
note.textContent = out.restartNote || ("Saved " + key + ".");
|
|
2183
|
+
} catch { note.textContent = "Could not reach the server to change " + key + "."; }
|
|
2171
2184
|
}
|
|
2172
2185
|
|
|
2173
2186
|
// List settings: add/remove one entry, then reload so the chips reflect disk truth.
|
|
@@ -2389,6 +2402,11 @@ export {
|
|
|
2389
2402
|
: s.update === "modified" ? '<span class="tag ext">modified</span>' : "";
|
|
2390
2403
|
const edit = '<button type="button" class="iconbtn" title="Edit" aria-label="Edit" data-name="' + esc(s.name) + '" data-action="edit" data-enigma="' + (s.source === "enigma" ? 1 : 0) + '">'
|
|
2391
2404
|
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg></button>';
|
|
2405
|
+
// Propose an improvement: open the skill's source on GitHub (view / edit / open a PR).
|
|
2406
|
+
const propose = s.source === "enigma"
|
|
2407
|
+
? '<button type="button" class="iconbtn" title="Propose an improvement (opens GitHub)" aria-label="Propose an improvement" data-name="' + esc(s.name) + '" data-action="propose">'
|
|
2408
|
+
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg></button>'
|
|
2409
|
+
: "";
|
|
2392
2410
|
const main = s.source === "enigma"
|
|
2393
2411
|
? '<button type="button" class="toggle' + (s.discarded ? "" : " on") + '" data-kind="bool" title="'
|
|
2394
2412
|
+ (s.discarded ? "Disabled - click to enable" : "Enabled - click to disable") + '" data-name="' + esc(s.name)
|
|
@@ -2405,10 +2423,12 @@ export {
|
|
|
2405
2423
|
+ '">' + (agentSvg(a.name) || esc(a.label)) + "</button>").join("") + "</div>"
|
|
2406
2424
|
: "";
|
|
2407
2425
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + edited + upd + where + "</div>"
|
|
2408
|
-
+ '<div class="set-hint">' + esc(s.description || "") + "</div>" + agentChips + '</div><div class="set-ctl" style="display:flex;gap:8px">' + edit + main + "</div></div>";
|
|
2426
|
+
+ '<div class="set-hint">' + esc(s.description || "") + "</div>" + agentChips + '</div><div class="set-ctl" style="display:flex;gap:8px">' + edit + propose + main + "</div></div>";
|
|
2409
2427
|
}
|
|
2410
2428
|
|
|
2429
|
+
let lastSkills = [];
|
|
2411
2430
|
function renderSkills(skills) {
|
|
2431
|
+
lastSkills = skills; // kept so the editor can offer per-app save targets
|
|
2412
2432
|
const enigma = skills.filter((s) => s.source === "enigma");
|
|
2413
2433
|
const ext = skills.filter((s) => s.source === "external");
|
|
2414
2434
|
const section = (title, blurb, list, emptyText) => '<div class="set-cat"><div class="set-cat-h">' + title + "</div>"
|
|
@@ -2440,8 +2460,9 @@ export {
|
|
|
2440
2460
|
if (!out.ok) { $("skillsNote").textContent = "Could not open " + name + ": " + (out.error || "error"); return; }
|
|
2441
2461
|
editing = name;
|
|
2442
2462
|
$("seName").textContent = name;
|
|
2443
|
-
$("seWarn").textContent = isEnigma ? "managed by enigma -
|
|
2463
|
+
$("seWarn").textContent = isEnigma ? "managed by enigma - see Customized-skill updates below for whether edits survive an update" : "external skill";
|
|
2444
2464
|
$("seText").value = out.content || "";
|
|
2465
|
+
editorAgents(lastSkills.find((s) => s.name === name));
|
|
2445
2466
|
$("skillEditor").style.display = "";
|
|
2446
2467
|
$("skillsNote").textContent = "";
|
|
2447
2468
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
@@ -2449,11 +2470,29 @@ export {
|
|
|
2449
2470
|
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
2450
2471
|
}
|
|
2451
2472
|
function closeEditor() { editing = null; $("skillEditor").style.display = "none"; }
|
|
2473
|
+
// Render per-app save targets (one checkbox per app that holds the skill), only when
|
|
2474
|
+
// the skill is on more than one app - with a single app there is nothing to choose.
|
|
2475
|
+
function editorAgents(skill) {
|
|
2476
|
+
const el = $("seAgents");
|
|
2477
|
+
const states = (skill && skill.agentStates) ? skill.agentStates.filter((a) => a.deployed) : [];
|
|
2478
|
+
if (states.length <= 1) { el.innerHTML = ""; el.dataset.has = "0"; return; }
|
|
2479
|
+
el.dataset.has = "1";
|
|
2480
|
+
el.innerHTML = '<span class="se-ag-label">Save to:</span>' + states.map((a) =>
|
|
2481
|
+
'<label class="se-ag"><input type="checkbox" checked data-agent="' + esc(a.name) + '">'
|
|
2482
|
+
+ '<span class="aicon" aria-hidden="true">' + (agentSvg(a.name) || "") + "</span>" + esc(a.label) + "</label>").join("");
|
|
2483
|
+
}
|
|
2452
2484
|
async function saveEditor() {
|
|
2453
2485
|
if (!editing) return;
|
|
2486
|
+
const agEl = $("seAgents");
|
|
2487
|
+
let content = $("seText").value;
|
|
2488
|
+
if (agEl.dataset.has === "1") {
|
|
2489
|
+
const agents = [...agEl.querySelectorAll("input[type=checkbox]:checked")].map((i) => i.dataset.agent);
|
|
2490
|
+
if (!agents.length) { $("skillsNote").textContent = "Pick at least one app to save to."; return; }
|
|
2491
|
+
content = { text: $("seText").value, agents }; // per-app save
|
|
2492
|
+
}
|
|
2454
2493
|
$("skillsNote").textContent = "Saving " + editing + "...";
|
|
2455
2494
|
try {
|
|
2456
|
-
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editing, action: "save", content
|
|
2495
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editing, action: "save", content }) });
|
|
2457
2496
|
const out = await res.json();
|
|
2458
2497
|
if (!out.ok) { $("skillsNote").textContent = "Could not save " + editing + ": " + (out.error || "error"); return; }
|
|
2459
2498
|
if (out.skills) renderSkills(out.skills);
|
|
@@ -2468,17 +2507,42 @@ export {
|
|
|
2468
2507
|
if (!b) return;
|
|
2469
2508
|
const { name, action } = b.dataset;
|
|
2470
2509
|
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
2510
|
+
if (action === "propose") { window.open("https://github.com/FJRG2007/enigma/blob/main/packages/enigma-cli/assets/skills/" + encodeURIComponent(name) + "/SKILL.md", "_blank", "noopener"); return; }
|
|
2471
2511
|
if (action === "agent-toggle") { postSkill(name, action, { agent: b.dataset.agent, off: b.dataset.off === "1" }); return; }
|
|
2472
2512
|
if (action === "remove" && !(await askConfirm("Remove skill", 'Remove the external skill "' + name + '" from all agents? This deletes it from disk.', true))) return;
|
|
2473
2513
|
postSkill(name, action);
|
|
2474
2514
|
});
|
|
2515
|
+
// Skill-related settings live in this tab too; reuse the settings POST, with the note here.
|
|
2516
|
+
$("skillSettings").addEventListener("click", (e) => {
|
|
2517
|
+
const t = e.target.closest("button.toggle[data-skey]");
|
|
2518
|
+
if (t) postSetting(t.dataset.skey, t.dataset.on !== "1", t, $("skillsNote"));
|
|
2519
|
+
});
|
|
2520
|
+
$("skillSettings").addEventListener("change", (e) => {
|
|
2521
|
+
const sel = e.target.closest("select.choice[data-skey]");
|
|
2522
|
+
if (sel) postSetting(sel.dataset.skey, sel.value, sel, $("skillsNote"));
|
|
2523
|
+
});
|
|
2475
2524
|
$("seSave").addEventListener("click", saveEditor);
|
|
2476
2525
|
$("seCancel").addEventListener("click", closeEditor);
|
|
2477
2526
|
$("skillsCheck").addEventListener("click", () => postSkill("*", "check-updates"));
|
|
2478
2527
|
}
|
|
2479
2528
|
|
|
2529
|
+
// The skill-related settings surfaced inside the Skills tab (a better-organised dashboard
|
|
2530
|
+
// than burying them under Settings). They still live in the one settings registry.
|
|
2531
|
+
const SKILL_SETTING_KEYS = ["remote-skills", "skill-update-policy", "auto-sync"];
|
|
2532
|
+
function renderSkillSettings(cats) {
|
|
2533
|
+
const all = (cats || []).flatMap((c) => c.settings || []);
|
|
2534
|
+
const rows = SKILL_SETTING_KEYS.map((k) => all.find((s) => s.key === k)).filter(Boolean);
|
|
2535
|
+
$("skillSettings").innerHTML = rows.length ? rows.map(settingRow).join("")
|
|
2536
|
+
: '<div class="empty" style="padding:8px 0">No skill settings available.</div>';
|
|
2537
|
+
}
|
|
2538
|
+
async function loadSkillSettings() {
|
|
2539
|
+
try { renderSkillSettings((await (await fetch("/api/settings", { cache: "no-store" })).json()).categories || []); }
|
|
2540
|
+
catch { $("skillSettings").innerHTML = ""; }
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2480
2543
|
let skillsLoaded = false;
|
|
2481
2544
|
async function loadSkills() {
|
|
2545
|
+
loadSkillSettings();
|
|
2482
2546
|
try {
|
|
2483
2547
|
const res = await fetch("/api/skills", { cache: "no-store" });
|
|
2484
2548
|
renderSkills((await res.json()).skills || []);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
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": [
|