@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.
@@ -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 ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&#39;' })[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)">&#x1F44B; 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 &rarr; Engine &rarr; 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 &rarr; 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 };
@@ -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 projOpts = (typeof cmdProjects !== 'undefined' ? cmdProjects : []).map(p => {
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">&#x26A0; 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 &mdash; 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>' +
@@ -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"><!-- __PAGES__ --></div>
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 -->
@@ -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.2002",
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"