@yemi33/minions 0.1.2002 → 0.1.2003
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/fre.js +196 -0
- package/dashboard/js/refresh.js +6 -0
- package/dashboard/js/render-prs.js +13 -1
- package/dashboard/layout.html +4 -1
- package/dashboard-build.js +1 -1
- package/dashboard.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// fre.js — First-Run Experience banner for empty-state dashboards (W-mpehchnt000w46da).
|
|
2
|
+
//
|
|
3
|
+
// Renders a two-step onboarding banner at the top of the main content area when
|
|
4
|
+
// the operator has zero projects configured AND has not explicitly dismissed the
|
|
5
|
+
// banner. Self-contained and idempotent — safe to call renderFre() every tick.
|
|
6
|
+
//
|
|
7
|
+
// Trigger: projects.length === 0 AND localStorage.minions_fre_dismissed !== '1'.
|
|
8
|
+
// Auto-hide (no dismissal flag set) when projects.length >= 1 — the banner has
|
|
9
|
+
// served its purpose. If the user later removes all projects we re-show.
|
|
10
|
+
|
|
11
|
+
const FRE_DISMISS_KEY = 'minions_fre_dismissed';
|
|
12
|
+
const FRE_MOUNT_ID = 'fre-banner';
|
|
13
|
+
|
|
14
|
+
function _freIsDismissed() {
|
|
15
|
+
try { return localStorage.getItem(FRE_DISMISS_KEY) === '1'; } catch { return false; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function dismissFre() {
|
|
19
|
+
try { localStorage.setItem(FRE_DISMISS_KEY, '1'); } catch { /* expected */ }
|
|
20
|
+
const mount = document.getElementById(FRE_MOUNT_ID);
|
|
21
|
+
if (mount) mount.innerHTML = '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Reset helper (exposed for tests / debugging only). Not wired to UI.
|
|
25
|
+
function _freReset() {
|
|
26
|
+
try { localStorage.removeItem(FRE_DISMISS_KEY); } catch { /* expected */ }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Open the Settings modal and scroll to the Default CLI control (id=set-defaultCli).
|
|
30
|
+
// The modal lives in a separate modal layer, not a sidebar page — openSettings()
|
|
31
|
+
// is async (loads /api/settings) so we await it before scrolling.
|
|
32
|
+
async function openSettingsToDefaultCli() {
|
|
33
|
+
if (typeof openSettings !== 'function') return;
|
|
34
|
+
try {
|
|
35
|
+
await openSettings();
|
|
36
|
+
} catch { /* settings fetch failure already toasts */ return; }
|
|
37
|
+
// openSettings injects innerHTML synchronously after the fetch resolves, so
|
|
38
|
+
// set-defaultCli is in the DOM by now. initRuntimeFleetUI() runs separately
|
|
39
|
+
// and hydrates options — but the <select> element itself is already present.
|
|
40
|
+
setTimeout(function() {
|
|
41
|
+
const el = document.getElementById('set-defaultCli');
|
|
42
|
+
if (!el) return;
|
|
43
|
+
try { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch { /* old browser */ }
|
|
44
|
+
try { el.focus({ preventScroll: true }); } catch { /* expected */ }
|
|
45
|
+
const prevOutline = el.style.outline;
|
|
46
|
+
el.style.outline = '2px solid var(--blue)';
|
|
47
|
+
el.style.outlineOffset = '2px';
|
|
48
|
+
setTimeout(function() { el.style.outline = prevOutline; el.style.outlineOffset = ''; }, 2000);
|
|
49
|
+
}, 50);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Render the FRE banner into #fre-banner. Idempotent — bails out early when:
|
|
53
|
+
// • mount point missing (layout didn't load yet)
|
|
54
|
+
// • banner explicitly dismissed
|
|
55
|
+
// • projects.length >= 1 (auto-hide)
|
|
56
|
+
// Otherwise renders the two-step card.
|
|
57
|
+
//
|
|
58
|
+
// Accepts the current /api/status payload (or just a projects array, for tests).
|
|
59
|
+
// When passed the full payload, autoMode.defaultCli drives the runtime label.
|
|
60
|
+
function renderFre(statusOrProjects) {
|
|
61
|
+
const mount = document.getElementById(FRE_MOUNT_ID);
|
|
62
|
+
if (!mount) return;
|
|
63
|
+
const isArr = Array.isArray(statusOrProjects);
|
|
64
|
+
const projects = isArr
|
|
65
|
+
? statusOrProjects
|
|
66
|
+
: (statusOrProjects && Array.isArray(statusOrProjects.projects) ? statusOrProjects.projects : []);
|
|
67
|
+
const status = isArr ? (window._lastStatus || {}) : (statusOrProjects || {});
|
|
68
|
+
const projectCount = projects.length;
|
|
69
|
+
// Bail-out paths assign an empty string literal on its own line so the
|
|
70
|
+
// SEC-03 dynamic-innerHTML counter (test/unit.test.js
|
|
71
|
+
// DYNAMIC_INNERHTML_BASELINE) treats them as exempt.
|
|
72
|
+
if (projectCount >= 1) {
|
|
73
|
+
mount.innerHTML = '';
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (_freIsDismissed()) {
|
|
77
|
+
mount.innerHTML = '';
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Resolve the currently-configured runtime CLI for the explainer copy.
|
|
82
|
+
// /api/status surfaces this as autoMode.defaultCli (resolveAgentCli(null, engine)).
|
|
83
|
+
// Fall back to autoMode.ccCli (also defaultCli-derived when ccCli unset) then 'claude'.
|
|
84
|
+
const auto = (status && status.autoMode) || {};
|
|
85
|
+
const runtimeCli = String(auto.defaultCli || auto.ccCli || 'claude');
|
|
86
|
+
|
|
87
|
+
const cardStyle = [
|
|
88
|
+
'margin:12px 24px',
|
|
89
|
+
'padding:16px 20px',
|
|
90
|
+
'background:var(--surface2)',
|
|
91
|
+
'border:1px solid var(--blue)',
|
|
92
|
+
'border-radius:var(--radius-lg)',
|
|
93
|
+
'color:var(--text)',
|
|
94
|
+
'font-size:13px',
|
|
95
|
+
'box-shadow:var(--shadow-md)',
|
|
96
|
+
].join(';');
|
|
97
|
+
|
|
98
|
+
const stepBox = [
|
|
99
|
+
'display:flex',
|
|
100
|
+
'gap:12px',
|
|
101
|
+
'align-items:flex-start',
|
|
102
|
+
'padding:10px 12px',
|
|
103
|
+
'background:var(--surface)',
|
|
104
|
+
'border:1px solid var(--border)',
|
|
105
|
+
'border-radius:var(--radius-sm)',
|
|
106
|
+
].join(';');
|
|
107
|
+
|
|
108
|
+
const stepNum = [
|
|
109
|
+
'display:inline-flex',
|
|
110
|
+
'align-items:center',
|
|
111
|
+
'justify-content:center',
|
|
112
|
+
'width:22px',
|
|
113
|
+
'height:22px',
|
|
114
|
+
'border-radius:var(--radius-full)',
|
|
115
|
+
'background:var(--blue)',
|
|
116
|
+
'color:#fff',
|
|
117
|
+
'font-weight:700',
|
|
118
|
+
'font-size:11px',
|
|
119
|
+
'flex-shrink:0',
|
|
120
|
+
].join(';');
|
|
121
|
+
|
|
122
|
+
const btnPrimary = [
|
|
123
|
+
'padding:6px 14px',
|
|
124
|
+
'background:var(--blue)',
|
|
125
|
+
'color:#fff',
|
|
126
|
+
'border:none',
|
|
127
|
+
'border-radius:var(--radius-sm)',
|
|
128
|
+
'cursor:pointer',
|
|
129
|
+
'font-size:12px',
|
|
130
|
+
'font-weight:600',
|
|
131
|
+
'margin-top:8px',
|
|
132
|
+
].join(';');
|
|
133
|
+
|
|
134
|
+
const btnGhost = [
|
|
135
|
+
'padding:6px 14px',
|
|
136
|
+
'background:transparent',
|
|
137
|
+
'color:var(--blue)',
|
|
138
|
+
'border:1px solid var(--blue)',
|
|
139
|
+
'border-radius:var(--radius-sm)',
|
|
140
|
+
'cursor:pointer',
|
|
141
|
+
'font-size:12px',
|
|
142
|
+
'font-weight:600',
|
|
143
|
+
'margin-top:8px',
|
|
144
|
+
].join(';');
|
|
145
|
+
|
|
146
|
+
const dismissBtn = [
|
|
147
|
+
'background:none',
|
|
148
|
+
'border:none',
|
|
149
|
+
'color:var(--muted)',
|
|
150
|
+
'cursor:pointer',
|
|
151
|
+
'font-size:11px',
|
|
152
|
+
'padding:2px 8px',
|
|
153
|
+
].join(';');
|
|
154
|
+
|
|
155
|
+
// Use textContent-safe interpolation for the runtime label (config-derived, but defensive).
|
|
156
|
+
const safeRuntime = String(runtimeCli).replace(/[<>&"']/g, function(c) {
|
|
157
|
+
return ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' })[c];
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
mount.innerHTML =
|
|
161
|
+
'<div id="fre-card" style="' + cardStyle + '">' +
|
|
162
|
+
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">' +
|
|
163
|
+
'<div style="font-size:15px;font-weight:700;color:var(--blue)">👋 Welcome to Minions</div>' +
|
|
164
|
+
'<button onclick="dismissFre()" style="' + dismissBtn + '" title="Hide this banner — clear localStorage.minions_fre_dismissed to re-show">Dismiss</button>' +
|
|
165
|
+
'</div>' +
|
|
166
|
+
'<div style="color:var(--muted);font-size:12px;margin-bottom:12px">Two quick steps to get your fleet ready.</div>' +
|
|
167
|
+
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
168
|
+
|
|
169
|
+
'<div style="' + stepBox + '">' +
|
|
170
|
+
'<span style="' + stepNum + '">1</span>' +
|
|
171
|
+
'<div style="flex:1">' +
|
|
172
|
+
'<div style="font-weight:600;margin-bottom:2px">Pick your runtime CLI</div>' +
|
|
173
|
+
'<div style="color:var(--muted);font-size:12px;line-height:1.5">' +
|
|
174
|
+
'Minions spawns agents through this CLI. Switch via Settings → Engine → Default CLI (<code>claude</code> or <code>copilot</code>). ' +
|
|
175
|
+
'Currently: <code style="background:var(--bg);padding:1px 6px;border-radius:3px;color:var(--text)">' + safeRuntime + '</code>' +
|
|
176
|
+
'</div>' +
|
|
177
|
+
'<button onclick="openSettingsToDefaultCli()" style="' + btnGhost + '">Open Settings → Default CLI</button>' +
|
|
178
|
+
'</div>' +
|
|
179
|
+
'</div>' +
|
|
180
|
+
|
|
181
|
+
'<div style="' + stepBox + '">' +
|
|
182
|
+
'<span style="' + stepNum + '">2</span>' +
|
|
183
|
+
'<div style="flex:1">' +
|
|
184
|
+
'<div style="font-weight:600;margin-bottom:2px">Add your first project</div>' +
|
|
185
|
+
'<div style="color:var(--muted);font-size:12px;line-height:1.5">' +
|
|
186
|
+
'Projects map a local git worktree to a remote repo. Without a project, agents have nowhere to run.' +
|
|
187
|
+
'</div>' +
|
|
188
|
+
'<button onclick="addProject()" style="' + btnPrimary + '">+ Add Project</button>' +
|
|
189
|
+
'</div>' +
|
|
190
|
+
'</div>' +
|
|
191
|
+
|
|
192
|
+
'</div>' +
|
|
193
|
+
'</div>';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
window.MinionsFre = { renderFre, dismissFre, openSettingsToDefaultCli, _freReset, FRE_DISMISS_KEY };
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -79,6 +79,12 @@ function _processStatusUpdate(data) {
|
|
|
79
79
|
if (_changed('prdProgress', data.prdProgress) || _changed('prdPrs', data.pullRequests?.length)) { renderPrdProgress(data.prdProgress); _cachePrdItems(data.prdProgress); }
|
|
80
80
|
if (_changed('inbox', data.inbox)) renderInbox(data.inbox || []);
|
|
81
81
|
if (_changed('projects', data.projects)) { cmdUpdateProjectList(data.projects || []); renderProjects(data.projects || []); }
|
|
82
|
+
// FRE banner — safe to call every tick (idempotent + cheap). Pass the full
|
|
83
|
+
// status payload so the runtime-CLI explainer reads autoMode.defaultCli from
|
|
84
|
+
// THIS tick rather than the previous one (window._lastStatus is set later).
|
|
85
|
+
if (typeof renderFre === 'function') {
|
|
86
|
+
try { renderFre(data); } catch { /* expected on first load */ }
|
|
87
|
+
}
|
|
82
88
|
if (_changed('notes', data.notes)) renderNotes(data.notes);
|
|
83
89
|
if (_changed('prd', [data.prd, data.prdProgress])) renderPrd(data.prd, data.prdProgress);
|
|
84
90
|
if (_changed('prs', data.pullRequests)) renderPrs(data.pullRequests || []);
|
|
@@ -135,15 +135,27 @@ function openModal(i) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
function openAddPrModal() {
|
|
138
|
-
const
|
|
138
|
+
const projects = (typeof cmdProjects !== 'undefined' ? cmdProjects : []) || [];
|
|
139
|
+
const projOpts = projects.map(p => {
|
|
139
140
|
const name = typeof p === 'object' ? p.name : p;
|
|
140
141
|
return '<option value="' + escapeHtml(name) + '">' + escapeHtml(name) + '</option>';
|
|
141
142
|
}).join('');
|
|
142
143
|
const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
|
|
143
144
|
|
|
145
|
+
// No-projects warning (W-mpehchnt000w46da) — surfaced when the operator hasn't
|
|
146
|
+
// added any project yet. We still allow the link (read-only tracking is valid),
|
|
147
|
+
// but warn that auto-fix / auto-review can't dispatch without a project worktree.
|
|
148
|
+
const noProjectsWarning = projects.length === 0
|
|
149
|
+
? '<div id="pr-link-no-projects-warning" style="padding:8px 12px;background:rgba(210,153,34,0.15);border:1px solid rgba(210,153,34,0.3);border-radius:var(--radius-sm);color:var(--yellow);font-size:11px;line-height:1.5;margin-bottom:4px">' +
|
|
150
|
+
'<div style="font-weight:700;margin-bottom:2px">⚠ No project configured.</div>' +
|
|
151
|
+
'<div style="color:var(--text)">This PR will be tracked, but auto-fix and auto-review cannot dispatch against it — agents need a project worktree to operate in. Add a project first (Projects tab) to enable automation.</div>' +
|
|
152
|
+
'</div>'
|
|
153
|
+
: '';
|
|
154
|
+
|
|
144
155
|
document.getElementById('modal-title').textContent = 'Link Pull Request';
|
|
145
156
|
document.getElementById('modal-body').innerHTML =
|
|
146
157
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
158
|
+
noProjectsWarning +
|
|
147
159
|
'<label style="color:var(--text);font-size:var(--text-md)">PR URL <input id="pr-link-url" style="' + inputStyle + '" placeholder="https://github.com/org/repo/pull/123"></label>' +
|
|
148
160
|
'<label style="color:var(--text);font-size:var(--text-md)">Title <input id="pr-link-title" style="' + inputStyle + '" placeholder="Short description (optional — auto-detected from URL)"></label>' +
|
|
149
161
|
'<label style="color:var(--text);font-size:var(--text-md)">Project <select id="pr-link-project" style="' + inputStyle + '"><option value="">Auto-detect from URL (central if no unique match)</option>' + projOpts + '</select></label>' +
|
package/dashboard/layout.html
CHANGED
|
@@ -78,7 +78,10 @@
|
|
|
78
78
|
<a class="sidebar-link" data-page="meetings" href="/meetings">Meetings</a>
|
|
79
79
|
<a class="sidebar-link" data-page="engine" href="/engine">Engine</a>
|
|
80
80
|
</nav>
|
|
81
|
-
<div class="page-content" id="page-content"
|
|
81
|
+
<div class="page-content" id="page-content">
|
|
82
|
+
<div id="fre-banner"></div>
|
|
83
|
+
<!-- __PAGES__ -->
|
|
84
|
+
</div>
|
|
82
85
|
</div>
|
|
83
86
|
|
|
84
87
|
<!-- Floating toast — lives outside .page divs so showToast() works on every page -->
|
package/dashboard-build.js
CHANGED
|
@@ -34,7 +34,7 @@ function buildDashboardHtml() {
|
|
|
34
34
|
'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
|
|
35
35
|
'render-other', 'render-managed', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
|
|
36
36
|
'command-parser', 'command-input', 'command-center', 'command-history',
|
|
37
|
-
'modal', 'modal-qa', 'settings', 'qa', 'refresh'
|
|
37
|
+
'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
|
|
38
38
|
];
|
|
39
39
|
let jsHtml = '';
|
|
40
40
|
for (const f of jsFiles) {
|
package/dashboard.js
CHANGED
|
@@ -980,7 +980,7 @@ function buildDashboardHtml() {
|
|
|
980
980
|
'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
|
|
981
981
|
'render-other', 'render-managed', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
|
|
982
982
|
'command-parser', 'command-input', 'command-center', 'command-history',
|
|
983
|
-
'modal', 'modal-qa', 'settings', 'qa', 'refresh'
|
|
983
|
+
'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
|
|
984
984
|
];
|
|
985
985
|
let jsHtml = '';
|
|
986
986
|
for (const f of jsFiles) {
|
|
@@ -1640,6 +1640,7 @@ function _buildStatusSlowState() {
|
|
|
1640
1640
|
ccCli: shared.resolveCcCli(CONFIG.engine),
|
|
1641
1641
|
ccModel: shared.resolveCcModel(CONFIG.engine),
|
|
1642
1642
|
ccEffort: CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort,
|
|
1643
|
+
defaultCli: shared.resolveAgentCli(null, CONFIG.engine),
|
|
1643
1644
|
},
|
|
1644
1645
|
initialized: !!(CONFIG.agents && Object.keys(CONFIG.agents).length > 0),
|
|
1645
1646
|
installId: safeRead(path.join(MINIONS_DIR, '.install-id')).trim() || null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2003",
|
|
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"
|