@yemi33/minions 0.1.2389 → 0.1.2391
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/dashboard/js/settings.js +76 -10
- package/dashboard/slim/body.html +7 -4
- package/dashboard/slim/js/settings.js +33 -92
- package/dashboard/slim/styles.css +8 -27
- package/dashboard/styles.css +15 -0
- package/package.json +1 -1
package/dashboard/js/settings.js
CHANGED
|
@@ -33,6 +33,38 @@ function _isCurrentModelLoad(scope, key, token) {
|
|
|
33
33
|
return _modelLoadEpochs[scope][key] === token;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Per-open dedup cache for /api/runtimes/<name>/models payloads.
|
|
37
|
+
// initRuntimeFleetUI() fans out one model fetch per agent PLUS default + CC.
|
|
38
|
+
// When the whole fleet shares a runtime (the common case) those are
|
|
39
|
+
// byte-identical requests, and each one makes the server reloadConfig() from
|
|
40
|
+
// disk + re-run model discovery. Collapsing them to one request per distinct
|
|
41
|
+
// runtime per Settings open removes the N+2 → 1 duplicate round-trips (measured
|
|
42
|
+
// 10 → 1 for an 8-agent single-runtime fleet). forceRefresh (the ↻ button)
|
|
43
|
+
// bypasses the cache and repopulates it with the fresh list. Transient
|
|
44
|
+
// failures are NOT cached so a blip can't poison every agent cell for the open.
|
|
45
|
+
let _modelFetchCache = new Map();
|
|
46
|
+
function _resetModelFetchCache() { _modelFetchCache = new Map(); }
|
|
47
|
+
function _fetchRuntimeModelsPayload(runtimeName, forceRefresh) {
|
|
48
|
+
if (!runtimeName) return Promise.resolve({ models: null });
|
|
49
|
+
if (!forceRefresh) {
|
|
50
|
+
const cached = _modelFetchCache.get(runtimeName);
|
|
51
|
+
if (cached) return cached;
|
|
52
|
+
}
|
|
53
|
+
const suffix = forceRefresh ? '/models/refresh' : '/models';
|
|
54
|
+
const p = fetch('/api/runtimes/' + encodeURIComponent(runtimeName) + suffix, forceRefresh ? { method: 'POST' } : undefined)
|
|
55
|
+
.then(res => (res.ok ? res.json() : null))
|
|
56
|
+
.catch(() => null)
|
|
57
|
+
.then(payload => {
|
|
58
|
+
if (!payload || !Array.isArray(payload.models)) {
|
|
59
|
+
if (_modelFetchCache.get(runtimeName) === p) _modelFetchCache.delete(runtimeName);
|
|
60
|
+
return payload || { models: null };
|
|
61
|
+
}
|
|
62
|
+
return payload;
|
|
63
|
+
});
|
|
64
|
+
_modelFetchCache.set(runtimeName, p);
|
|
65
|
+
return p;
|
|
66
|
+
}
|
|
67
|
+
|
|
36
68
|
async function openSettings() {
|
|
37
69
|
document.getElementById('modal-title').textContent = 'Settings';
|
|
38
70
|
document.getElementById('modal-body').innerHTML = '<p style="color:var(--muted)">Loading...</p>';
|
|
@@ -719,8 +751,16 @@ async function openSettings() {
|
|
|
719
751
|
// hides unmatched rows so operators can see results without tab-hopping.
|
|
720
752
|
const searchInput = document.getElementById('settings-search');
|
|
721
753
|
if (searchInput) {
|
|
754
|
+
// Debounce the filter: _applySettingsSearch runs several full-DOM
|
|
755
|
+
// querySelectorAll scans and reads textContent of every [data-search] row
|
|
756
|
+
// (a forced reflow). Firing that per-keystroke made typing janky on the
|
|
757
|
+
// large Settings DOM. The field itself stays instant; only the filter
|
|
758
|
+
// computation is coalesced to the last keystroke in a ~120ms window.
|
|
759
|
+
let _searchDebounce = null;
|
|
722
760
|
searchInput.addEventListener('input', function() {
|
|
723
|
-
|
|
761
|
+
const val = searchInput.value || '';
|
|
762
|
+
if (_searchDebounce) clearTimeout(_searchDebounce);
|
|
763
|
+
_searchDebounce = setTimeout(function() { _applySettingsSearch(val); }, 120);
|
|
724
764
|
});
|
|
725
765
|
}
|
|
726
766
|
|
|
@@ -822,6 +862,9 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
|
822
862
|
const ccCliSelect = document.getElementById('set-ccCli');
|
|
823
863
|
if (!cliSelect || !ccCliSelect) return;
|
|
824
864
|
|
|
865
|
+
// Fresh cache per Settings open so a config change between opens is picked up.
|
|
866
|
+
_resetModelFetchCache();
|
|
867
|
+
|
|
825
868
|
// Fetch the registry; render an empty fallback on failure so the rest of the
|
|
826
869
|
// settings panel still works.
|
|
827
870
|
let runtimes = [];
|
|
@@ -917,11 +960,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue, forceRef
|
|
|
917
960
|
return;
|
|
918
961
|
}
|
|
919
962
|
let payload = { models: null };
|
|
920
|
-
|
|
921
|
-
const suffix = forceRefresh ? '/models/refresh' : '/models';
|
|
922
|
-
const res = await fetch('/api/runtimes/' + encodeURIComponent(runtimeName) + suffix, forceRefresh ? { method: 'POST' } : undefined);
|
|
923
|
-
if (res.ok) payload = await res.json();
|
|
924
|
-
} catch { /* fall through to free-text */ }
|
|
963
|
+
payload = await _fetchRuntimeModelsPayload(runtimeName, forceRefresh);
|
|
925
964
|
|
|
926
965
|
if (!_isCurrentModelLoad('runtime', inputId, token)) return;
|
|
927
966
|
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
@@ -962,10 +1001,7 @@ async function loadModelsForAgent(agentId, runtimeName, currentValue) {
|
|
|
962
1001
|
return;
|
|
963
1002
|
}
|
|
964
1003
|
let payload = { models: null };
|
|
965
|
-
|
|
966
|
-
const res = await fetch('/api/runtimes/' + encodeURIComponent(runtimeName) + '/models');
|
|
967
|
-
if (res.ok) payload = await res.json();
|
|
968
|
-
} catch { /* fall through to free-text */ }
|
|
1004
|
+
payload = await _fetchRuntimeModelsPayload(runtimeName, false);
|
|
969
1005
|
|
|
970
1006
|
if (!_isCurrentModelLoad('agent', agentId, token)) return;
|
|
971
1007
|
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
@@ -1445,3 +1481,33 @@ async function removeProject(name) {
|
|
|
1445
1481
|
}
|
|
1446
1482
|
|
|
1447
1483
|
window.MinionsSettings = { openSettings, saveSettings, addProject, removeProject, resetSettingsToDefaults, _selectSettingsTab, _applySettingsSearch };
|
|
1484
|
+
|
|
1485
|
+
// ── Settings embed mode (Slim UX parity — W-mrkpke07000i8ed5) ───────────
|
|
1486
|
+
// The Slim UX Settings modal embeds this LITERAL classic Settings surface in
|
|
1487
|
+
// an iframe at /settings?embed=1 (chrome-off) so there is ONE settings UI, not
|
|
1488
|
+
// two (CLAUDE.md "Slim UX vs Classic UX — reuse, don't fork"; mirrors the
|
|
1489
|
+
// Queued-work/Plans/PRs cockpit tiles). Slim and classic are separate IIFE
|
|
1490
|
+
// bundles with no shared runtime scope, so slim can't call openSettings()
|
|
1491
|
+
// in-process — it iframes the real classic screen instead. On boot, when we
|
|
1492
|
+
// ARE the embedded /settings page, auto-open the modal and tag <html> so the
|
|
1493
|
+
// embed CSS renders the settings modal full-bleed inside the iframe. Only the
|
|
1494
|
+
// /settings path triggers this; every other embedded page (/work, /plans,
|
|
1495
|
+
// /prs) is untouched.
|
|
1496
|
+
(function bootSettingsEmbed() {
|
|
1497
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
1498
|
+
try {
|
|
1499
|
+
var isSettingsPath = window.location.pathname.replace(/\/+$/, '') === '/settings';
|
|
1500
|
+
var isEmbed = /[?&]embed=1(?:&|$)/.test(window.location.search);
|
|
1501
|
+
if (!isSettingsPath || !isEmbed) return;
|
|
1502
|
+
} catch (e) { return; }
|
|
1503
|
+
try { document.documentElement.classList.add('embed-settings'); } catch (e) { /* no DOM attr */ }
|
|
1504
|
+
function openWhenReady() {
|
|
1505
|
+
if (typeof openSettings === 'function') { openSettings(); return; }
|
|
1506
|
+
setTimeout(openWhenReady, 30); // wait for the settings module + deps to register
|
|
1507
|
+
}
|
|
1508
|
+
if (document.readyState === 'loading') {
|
|
1509
|
+
document.addEventListener('DOMContentLoaded', function() { setTimeout(openWhenReady, 0); });
|
|
1510
|
+
} else {
|
|
1511
|
+
setTimeout(openWhenReady, 0);
|
|
1512
|
+
}
|
|
1513
|
+
})();
|
package/dashboard/slim/body.html
CHANGED
|
@@ -270,16 +270,19 @@
|
|
|
270
270
|
</div>
|
|
271
271
|
</div>
|
|
272
272
|
|
|
273
|
-
<!-- Settings dialog
|
|
274
|
-
|
|
273
|
+
<!-- Settings dialog — embeds the LITERAL classic dashboard Settings surface
|
|
274
|
+
in an iframe (/settings?embed=1) so slim shows the FULL settings (engine
|
|
275
|
+
toggles, runtime/model, projects, agents, routing, feature flags incl.
|
|
276
|
+
slim-ux) with zero forked rendering. See dashboard/slim/js/settings.js
|
|
277
|
+
(W-mrkpke07000i8ed5 / "reuse, don't fork"). -->
|
|
275
278
|
<div class="modal-bg" id="slim-settings-modal">
|
|
276
279
|
<div class="modal">
|
|
277
280
|
<div class="modal-header">
|
|
278
|
-
<h3>Settings
|
|
281
|
+
<h3>Settings</h3>
|
|
279
282
|
<button id="slim-settings-close" class="icon-btn" title="Close">×</button>
|
|
280
283
|
</div>
|
|
281
284
|
<div class="modal-body" id="slim-settings-body">
|
|
282
|
-
<p>Loading
|
|
285
|
+
<p>Loading settings…</p>
|
|
283
286
|
</div>
|
|
284
287
|
</div>
|
|
285
288
|
</div>
|
|
@@ -1,80 +1,39 @@
|
|
|
1
1
|
// ── Settings dialog ────────────────────────────────────────────
|
|
2
|
-
|
|
2
|
+
// Reuses the LITERAL classic dashboard Settings surface instead of a
|
|
3
|
+
// slim-only feature-flag list, so there is ONE settings UI, not two
|
|
4
|
+
// (W-mrkpke07000i8ed5 / CLAUDE.md "Slim UX vs Classic UX — reuse, don't
|
|
5
|
+
// fork"; mirrors the Queued-work/Plans/PRs cockpit tiles). Slim and classic
|
|
6
|
+
// are two separate IIFE bundles with no shared runtime scope, so we can't
|
|
7
|
+
// call the classic openSettings() in-process; instead we embed the real
|
|
8
|
+
// classic /settings screen in an iframe with the chrome-off ?embed=1 mode.
|
|
9
|
+
// The iframed page IS the classic Settings modal — every engine toggle,
|
|
10
|
+
// runtime/model, projects, agents, routing, feature flags (incl. slim-ux) —
|
|
11
|
+
// backed by the same /api/settings + /api/features endpoints, with zero
|
|
12
|
+
// duplicated rendering logic. Classic is reachable at /settings even with
|
|
13
|
+
// slim-ux ON (only / is taken over). Built with createElement (no innerHTML)
|
|
14
|
+
// to satisfy the dashboard no-unsanitized lint gate.
|
|
15
|
+
function openSlimSettings() {
|
|
3
16
|
var modal = document.getElementById('slim-settings-modal');
|
|
4
17
|
var body = document.getElementById('slim-settings-body');
|
|
5
|
-
|
|
18
|
+
if (!modal || !body) return;
|
|
19
|
+
body.textContent = '';
|
|
20
|
+
var frame = document.createElement('iframe');
|
|
21
|
+
frame.className = 'slim-settings-embed';
|
|
22
|
+
frame.src = '/settings?embed=1';
|
|
23
|
+
frame.title = 'Settings';
|
|
24
|
+
frame.addEventListener('load', function() {
|
|
25
|
+
var frameDocument = frame.contentDocument;
|
|
26
|
+
if (!frameDocument) return;
|
|
27
|
+
// Capture before the classic modal handler so only the parent modal closes.
|
|
28
|
+
frameDocument.addEventListener('keydown', function(ev) {
|
|
29
|
+
if (ev.key !== 'Escape' && ev.keyCode !== 27) return;
|
|
30
|
+
ev.preventDefault();
|
|
31
|
+
ev.stopImmediatePropagation();
|
|
32
|
+
closeSlimSettings();
|
|
33
|
+
}, true);
|
|
34
|
+
});
|
|
35
|
+
body.appendChild(frame);
|
|
6
36
|
modal.classList.add('open');
|
|
7
|
-
try {
|
|
8
|
-
var res = await fetch('/api/features');
|
|
9
|
-
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
10
|
-
var data = await res.json();
|
|
11
|
-
var flags = (data && data.features) || [];
|
|
12
|
-
|
|
13
|
-
// Build the body as a document fragment so we avoid .innerHTML for
|
|
14
|
-
// the dynamic parts (SEC-03 ratchet — see DYNAMIC_INNERHTML_BASELINE
|
|
15
|
-
// in test/unit.test.js). Static intro paragraph uses innerHTML
|
|
16
|
-
// because it carries a <code> tag and is a pure string literal
|
|
17
|
-
// (exempted by _isStaticInnerHtmlRhs).
|
|
18
|
-
var intro = document.createElement('p');
|
|
19
|
-
intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
|
|
20
|
-
var frag = document.createDocumentFragment();
|
|
21
|
-
frag.appendChild(intro);
|
|
22
|
-
|
|
23
|
-
if (flags.length === 0) {
|
|
24
|
-
var none = document.createElement('p');
|
|
25
|
-
none.innerHTML = 'No experimental flags registered. Add entries to <code>engine/features.js</code>.';
|
|
26
|
-
frag.appendChild(none);
|
|
27
|
-
} else {
|
|
28
|
-
for (var i = 0; i < flags.length; i++) {
|
|
29
|
-
var f = flags[i];
|
|
30
|
-
var row = document.createElement('div');
|
|
31
|
-
row.className = 'flag-row';
|
|
32
|
-
var info = document.createElement('div');
|
|
33
|
-
var name = document.createElement('div');
|
|
34
|
-
name.className = 'flag-name';
|
|
35
|
-
name.textContent = f.id;
|
|
36
|
-
var desc = document.createElement('div');
|
|
37
|
-
desc.className = 'flag-desc';
|
|
38
|
-
desc.textContent = f.description || '';
|
|
39
|
-
info.appendChild(name);
|
|
40
|
-
info.appendChild(desc);
|
|
41
|
-
var lbl = document.createElement('label');
|
|
42
|
-
lbl.className = 'flag-toggle';
|
|
43
|
-
var input = document.createElement('input');
|
|
44
|
-
input.type = 'checkbox';
|
|
45
|
-
input.setAttribute('data-flag', f.id);
|
|
46
|
-
if (f.enabled) input.checked = true;
|
|
47
|
-
var stateSpan = document.createElement('span');
|
|
48
|
-
stateSpan.textContent = f.enabled ? 'On' : 'Off';
|
|
49
|
-
lbl.appendChild(input);
|
|
50
|
-
lbl.appendChild(stateSpan);
|
|
51
|
-
row.appendChild(info);
|
|
52
|
-
row.appendChild(lbl);
|
|
53
|
-
frag.appendChild(row);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// TEMP: deep-link to the full dashboard's settings page. The slim
|
|
58
|
-
// intentionally doesn't reimplement that whole UI — Carlos asked us
|
|
59
|
-
// to keep settings sprawl off the main slim view.
|
|
60
|
-
var link = document.createElement('a');
|
|
61
|
-
link.className = 'settings-link';
|
|
62
|
-
link.href = '/?fullDashboard=1';
|
|
63
|
-
link.title = 'Open the full dashboard’s settings page';
|
|
64
|
-
link.textContent = 'Advanced settings → full dashboard';
|
|
65
|
-
frag.appendChild(link);
|
|
66
|
-
|
|
67
|
-
body.replaceChildren(frag);
|
|
68
|
-
var inputs = body.querySelectorAll('input[type=checkbox][data-flag]');
|
|
69
|
-
for (var j = 0; j < inputs.length; j++) {
|
|
70
|
-
inputs[j].addEventListener('change', onFlagToggle);
|
|
71
|
-
}
|
|
72
|
-
} catch (e) {
|
|
73
|
-
var err = document.createElement('p');
|
|
74
|
-
err.style.color = 'var(--red)';
|
|
75
|
-
err.textContent = 'Failed to load flags: ' + (e && e.message ? e.message : 'unknown error');
|
|
76
|
-
body.replaceChildren(err);
|
|
77
|
-
}
|
|
78
37
|
}
|
|
79
38
|
|
|
80
39
|
function closeSlimSettings() {
|
|
@@ -99,7 +58,7 @@
|
|
|
99
58
|
})();
|
|
100
59
|
|
|
101
60
|
// POST a feature-flag toggle; throws on a non-2xx so callers can branch on
|
|
102
|
-
// failure.
|
|
61
|
+
// failure. Used by the one-click returnToClassicDashboard().
|
|
103
62
|
async function postFeatureToggle(id, enabled) {
|
|
104
63
|
var res = await fetch('/api/features/toggle', {
|
|
105
64
|
method: 'POST',
|
|
@@ -223,24 +182,6 @@
|
|
|
223
182
|
bindModalClose('slim-bug-modal', 'slim-bug-close');
|
|
224
183
|
})();
|
|
225
184
|
|
|
226
|
-
async function onFlagToggle(ev) {
|
|
227
|
-
var input = ev.currentTarget;
|
|
228
|
-
var id = input.getAttribute('data-flag');
|
|
229
|
-
var enabled = input.checked;
|
|
230
|
-
var label = input.parentElement.querySelector('span');
|
|
231
|
-
if (label) label.textContent = enabled ? 'On' : 'Off';
|
|
232
|
-
try {
|
|
233
|
-
await postFeatureToggle(id, enabled);
|
|
234
|
-
if (id === 'slim-ux' && !enabled) {
|
|
235
|
-
location.reload();
|
|
236
|
-
}
|
|
237
|
-
} catch (e) {
|
|
238
|
-
input.checked = !enabled;
|
|
239
|
-
if (label) label.textContent = !enabled ? 'On' : 'Off';
|
|
240
|
-
alert('Toggle failed: ' + e.message);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
185
|
// ── Action prompt modal (TEMP: routes through chat) ──────────────
|
|
245
186
|
// Each of the 4 action buttons opens this modal with prompt text
|
|
246
187
|
// tailored to the concept. On submit we drop the user's text into the
|
|
@@ -690,9 +690,16 @@
|
|
|
690
690
|
#slim-tile-modal.tile-modal--work .modal-body,
|
|
691
691
|
#slim-tile-modal.tile-modal--plans .modal-body,
|
|
692
692
|
#slim-tile-modal.tile-modal--prs .modal-body { padding: 0; }
|
|
693
|
+
/* W-mrkpke07000i8ed5 — the Settings modal embeds the full classic Settings
|
|
694
|
+
screen (/settings?embed=1) in an iframe; widen the modal + drop the body
|
|
695
|
+
padding so the embedded settings surface (left rail + content) gets the
|
|
696
|
+
full width/height. Mirrors the Queued-work/Plans/PRs tile treatment. */
|
|
697
|
+
#slim-settings-modal .modal { width: 1180px; max-width: calc(100vw - 32px); }
|
|
698
|
+
#slim-settings-modal .modal-body { padding: 0; }
|
|
693
699
|
.slim-work-embed,
|
|
694
700
|
.slim-plans-embed,
|
|
695
|
-
.slim-prs-embed
|
|
701
|
+
.slim-prs-embed,
|
|
702
|
+
.slim-settings-embed {
|
|
696
703
|
display: block; width: 100%; height: calc(100vh - 160px); min-height: 360px;
|
|
697
704
|
border: 0; background: var(--bg);
|
|
698
705
|
}
|
|
@@ -1705,32 +1712,6 @@
|
|
|
1705
1712
|
}
|
|
1706
1713
|
.btn-primary:hover, .btn-secondary:hover { filter: brightness(1.1); }
|
|
1707
1714
|
|
|
1708
|
-
.flag-row {
|
|
1709
|
-
display: flex;
|
|
1710
|
-
align-items: center;
|
|
1711
|
-
justify-content: space-between;
|
|
1712
|
-
padding: 10px 0;
|
|
1713
|
-
border-bottom: 1px solid var(--border);
|
|
1714
|
-
}
|
|
1715
|
-
.flag-row:last-child { border-bottom: none; }
|
|
1716
|
-
.flag-name { font-size: var(--text-lg); font-weight: 600; }
|
|
1717
|
-
.flag-desc { font-size: var(--text-base); color: var(--muted); margin-top: 2px; }
|
|
1718
|
-
.flag-toggle { display: flex; align-items: center; gap: 8px; font-size: var(--text-md); }
|
|
1719
|
-
.flag-toggle input { cursor: pointer; }
|
|
1720
|
-
.settings-link {
|
|
1721
|
-
display: block;
|
|
1722
|
-
margin-top: 14px;
|
|
1723
|
-
padding: 10px 12px;
|
|
1724
|
-
background: var(--surface2);
|
|
1725
|
-
border: 1px solid var(--border);
|
|
1726
|
-
border-radius: var(--radius);
|
|
1727
|
-
color: var(--blue);
|
|
1728
|
-
text-decoration: none;
|
|
1729
|
-
font-size: var(--text-md);
|
|
1730
|
-
text-align: center;
|
|
1731
|
-
}
|
|
1732
|
-
.settings-link:hover { filter: brightness(1.1); }
|
|
1733
|
-
|
|
1734
1715
|
.modal-textarea {
|
|
1735
1716
|
width: 100%;
|
|
1736
1717
|
min-height: 96px;
|
package/dashboard/styles.css
CHANGED
|
@@ -1645,3 +1645,18 @@
|
|
|
1645
1645
|
base .page-layout flex row already gives .page-content full width plus a
|
|
1646
1646
|
bounded height to scroll within. */
|
|
1647
1647
|
html.embed .page-content { padding: 12px 16px; overflow-y: auto; }
|
|
1648
|
+
|
|
1649
|
+
/* W-mrkpke07000i8ed5 — settings-embed mode. The Slim UX Settings modal iframes
|
|
1650
|
+
the classic Settings surface at /settings?embed=1; settings.js#bootSettingsEmbed
|
|
1651
|
+
auto-opens the modal and tags <html class="embed-settings">. Render the
|
|
1652
|
+
settings modal full-bleed inside the iframe (no dim backdrop, no rounded
|
|
1653
|
+
card, fill the frame), hide the empty home page-content behind it, and hide
|
|
1654
|
+
the modal close X — the parent slim modal owns closing. */
|
|
1655
|
+
html.embed-settings .page-content { display: none !important; }
|
|
1656
|
+
html.embed-settings #modal.modal-bg { background: var(--bg); }
|
|
1657
|
+
html.embed-settings #modal .modal,
|
|
1658
|
+
html.embed-settings #modal .modal.modal-wide {
|
|
1659
|
+
width: 100%; max-width: none; height: 100%; max-height: none;
|
|
1660
|
+
border: none; border-radius: 0;
|
|
1661
|
+
}
|
|
1662
|
+
html.embed-settings #modal .modal-close { display: none; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2391",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|