@yemi33/minions 0.1.123 → 0.1.124

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.124 (2026-04-01)
4
+
5
+ ### Dashboard
6
+ - dashboard/js/render-schedules.js
7
+ - dashboard/styles.css
8
+
3
9
  ## 0.1.123 (2026-04-01)
4
10
 
5
11
  ### Engine
@@ -179,6 +179,89 @@ async function _parseNaturalCron() {
179
179
 
180
180
  let _schedPage = 0;
181
181
  const SCHED_PER_PAGE = 15;
182
+ let _schedViewMode = 'list';
183
+
184
+ const _SLOT_COLORS = {
185
+ implement: { bg: 'rgba(88,166,255,0.15)', border: 'var(--blue)', text: 'var(--blue)' },
186
+ review: { bg: 'rgba(188,140,255,0.15)', border: 'var(--purple)', text: 'var(--purple)' },
187
+ fix: { bg: 'rgba(210,153,34,0.15)', border: 'var(--yellow)', text: 'var(--yellow)' },
188
+ explore: { bg: 'rgba(139,148,158,0.15)', border: 'var(--muted)', text: 'var(--muted)' },
189
+ test: { bg: 'rgba(227,179,65,0.15)', border: 'var(--orange)', text: 'var(--orange)' },
190
+ ask: { bg: 'rgba(63,185,80,0.15)', border: 'var(--green)', text: 'var(--green)' },
191
+ };
192
+
193
+ function _renderViewToggle() {
194
+ const isList = _schedViewMode === 'list';
195
+ return '<div style="display:flex;gap:4px;margin-bottom:8px">' +
196
+ '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (isList ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="_schedSetView(\'list\')">List</button>' +
197
+ '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (!isList ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="_schedSetView(\'calendar\')">Calendar</button>' +
198
+ '</div>';
199
+ }
200
+
201
+ function _schedSetView(mode) {
202
+ _schedViewMode = mode;
203
+ renderSchedules(window._lastSchedules || []);
204
+ }
205
+
206
+ function _renderScheduleCalendar(schedules) {
207
+ // Parse each schedule into day+hour slots
208
+ const slots = []; // { schedule, day, hour, minute }
209
+ for (const s of schedules) {
210
+ const p = _parseCronToPicker(s.cron || '');
211
+ for (const day of p.days) {
212
+ slots.push({ schedule: s, day: day, hour: p.hour, minute: p.minute });
213
+ }
214
+ }
215
+
216
+ // Find hours that have slots (compact — skip empty hours)
217
+ const hoursUsed = new Set();
218
+ for (const sl of slots) hoursUsed.add(sl.hour);
219
+ const hours = [...hoursUsed].sort(function(a, b) { return a - b; });
220
+
221
+ if (hours.length === 0) {
222
+ return '<p class="empty">No schedules to show in calendar view.</p>';
223
+ }
224
+
225
+ // Build grid: header row + one row per hour
226
+ // Columns: Mon(1) Tue(2) Wed(3) Thu(4) Fri(5) Sat(6) Sun(0)
227
+ var dayOrder = [1, 2, 3, 4, 5, 6, 0];
228
+ var dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
229
+
230
+ var html = '<div class="sched-cal">';
231
+ // Header row
232
+ html += '<div class="sched-cal-header"></div>'; // empty top-left
233
+ for (var d = 0; d < 7; d++) {
234
+ html += '<div class="sched-cal-header">' + dayLabels[d] + '</div>';
235
+ }
236
+
237
+ // One row per hour
238
+ for (var hi = 0; hi < hours.length; hi++) {
239
+ var hour = hours[hi];
240
+ html += '<div class="sched-cal-hour">' + String(hour).padStart(2, '0') + ':00</div>';
241
+ for (var di = 0; di < 7; di++) {
242
+ var dayNum = dayOrder[di];
243
+ var cellSlots = slots.filter(function(sl) { return sl.hour === hour && sl.day === dayNum; });
244
+ html += '<div class="sched-cal-cell">';
245
+ for (var si = 0; si < cellSlots.length; si++) {
246
+ var sl = cellSlots[si];
247
+ var s = sl.schedule;
248
+ var colors = _SLOT_COLORS[s.type || 'implement'] || _SLOT_COLORS.implement;
249
+ var opacity = s.enabled === false ? '0.4' : '1';
250
+ var strikeStyle = s.enabled === false ? 'text-decoration:line-through;' : '';
251
+ var timeLabel = String(sl.hour).padStart(2, '0') + ':' + String(sl.minute).padStart(2, '0');
252
+ html += '<div class="sched-cal-slot" style="background:' + colors.bg + ';border-left-color:' + colors.border + ';color:' + colors.text + ';opacity:' + opacity + '" ' +
253
+ 'onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')" title="' + escHtml(s.title + ' — ' + timeLabel + ' — ' + (s.type || 'implement')) + '">' +
254
+ '<span style="font-weight:600;' + strikeStyle + '">' + escHtml((s.title || s.id).slice(0, 25)) + '</span>' +
255
+ '<span style="font-size:9px;opacity:0.7"> ' + timeLabel + '</span>' +
256
+ '</div>';
257
+ }
258
+ if (cellSlots.length === 0) html += '&nbsp;';
259
+ html += '</div>';
260
+ }
261
+ }
262
+ html += '</div>';
263
+ return html;
264
+ }
182
265
 
183
266
  function renderSchedules(schedules) {
184
267
  schedules = schedules.filter(function(s) { return !isDeleted('sched:' + s.id); });
@@ -191,45 +274,51 @@ function renderSchedules(schedules) {
191
274
  return;
192
275
  }
193
276
 
194
- const totalPages = Math.ceil(schedules.length / SCHED_PER_PAGE);
195
- if (_schedPage >= totalPages) _schedPage = totalPages - 1;
196
- const start = _schedPage * SCHED_PER_PAGE;
197
- const pageItems = schedules.slice(start, start + SCHED_PER_PAGE);
198
-
199
- let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Schedule</th><th>Type</th><th>Project</th><th>Agent</th><th>Enabled</th><th>Last Run</th><th></th></tr></thead><tbody>';
200
- for (const s of pageItems) {
201
- const enabledBadge = s.enabled
202
- ? '<span class="pr-badge approved">enabled</span>'
203
- : '<span class="pr-badge rejected">disabled</span>';
204
- const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
205
- const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
206
- const humanCron = _cronToHuman(s.cron || '');
207
- html += '<tr style="cursor:pointer" onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
208
- '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
209
- '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
210
- '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
211
- '<td>' + typeBadge + '</td>' +
212
- '<td><span style="font-size:10px;color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
213
- '<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
214
- '<td>' + enabledBadge + '</td>' +
215
- '<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
216
- '<td style="white-space:nowrap">' +
217
- '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '&#x23F8;' : '&#x25B6;') + '</button>' +
218
- '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">&#x270E;</button>' +
219
- '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">&#x2715;</button>' +
220
- '</td>' +
221
- '</tr>';
222
- }
223
- html += '</tbody></table></div>';
224
-
225
- if (schedules.length > SCHED_PER_PAGE) {
226
- html += '<div class="pr-pager">' +
227
- '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+SCHED_PER_PAGE, schedules.length) + ' of ' + schedules.length + '</span>' +
228
- '<div class="pr-pager-btns">' +
229
- '<button class="pr-pager-btn ' + (_schedPage === 0 ? 'disabled' : '') + '" onclick="_schedPrev()">Prev</button>' +
230
- '<button class="pr-pager-btn ' + (_schedPage >= totalPages-1 ? 'disabled' : '') + '" onclick="_schedNext()">Next</button>' +
231
- '</div>' +
232
- '</div>';
277
+ let html = _renderViewToggle();
278
+
279
+ if (_schedViewMode === 'calendar') {
280
+ html += _renderScheduleCalendar(schedules);
281
+ } else {
282
+ const totalPages = Math.ceil(schedules.length / SCHED_PER_PAGE);
283
+ if (_schedPage >= totalPages) _schedPage = totalPages - 1;
284
+ const start = _schedPage * SCHED_PER_PAGE;
285
+ const pageItems = schedules.slice(start, start + SCHED_PER_PAGE);
286
+
287
+ html += '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Schedule</th><th>Type</th><th>Project</th><th>Agent</th><th>Enabled</th><th>Last Run</th><th></th></tr></thead><tbody>';
288
+ for (const s of pageItems) {
289
+ const enabledBadge = s.enabled
290
+ ? '<span class="pr-badge approved">enabled</span>'
291
+ : '<span class="pr-badge rejected">disabled</span>';
292
+ const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
293
+ const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
294
+ const humanCron = _cronToHuman(s.cron || '');
295
+ html += '<tr style="cursor:pointer" onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
296
+ '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
297
+ '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
298
+ '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
299
+ '<td>' + typeBadge + '</td>' +
300
+ '<td><span style="font-size:10px;color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
301
+ '<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
302
+ '<td>' + enabledBadge + '</td>' +
303
+ '<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
304
+ '<td style="white-space:nowrap">' +
305
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '&#x23F8;' : '&#x25B6;') + '</button>' +
306
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">&#x270E;</button>' +
307
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">&#x2715;</button>' +
308
+ '</td>' +
309
+ '</tr>';
310
+ }
311
+ html += '</tbody></table></div>';
312
+
313
+ if (schedules.length > SCHED_PER_PAGE) {
314
+ html += '<div class="pr-pager">' +
315
+ '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+SCHED_PER_PAGE, schedules.length) + ' of ' + schedules.length + '</span>' +
316
+ '<div class="pr-pager-btns">' +
317
+ '<button class="pr-pager-btn ' + (_schedPage === 0 ? 'disabled' : '') + '" onclick="_schedPrev()">Prev</button>' +
318
+ '<button class="pr-pager-btn ' + (_schedPage >= totalPages-1 ? 'disabled' : '') + '" onclick="_schedNext()">Next</button>' +
319
+ '</div>' +
320
+ '</div>';
321
+ }
233
322
  }
234
323
 
235
324
  el.innerHTML = html;
@@ -463,4 +552,4 @@ async function deleteSchedule(id) {
463
552
  // Expose _generateScheduleId globally for the inline oninput handler
464
553
  window._generateScheduleId = _generateScheduleId;
465
554
 
466
- window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, openScheduleDetail, submitSchedule, toggleScheduleEnabled, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview, _schedPrev, _schedNext };
555
+ window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, openScheduleDetail, submitSchedule, toggleScheduleEnabled, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview, _schedPrev, _schedNext, _schedSetView };
@@ -131,6 +131,14 @@
131
131
  .token-bar:hover .token-bar-tip { display: block; }
132
132
  .token-chart-labels { display: flex; gap: 3px; }
133
133
  .token-chart-labels span { flex: 1; min-width: 8px; max-width: 24px; font-size: 8px; color: var(--muted); text-align: center; overflow: hidden; }
134
+ /* Schedule calendar */
135
+ .sched-cal { display: grid; grid-template-columns: 50px repeat(7, 1fr); gap: 1px; background: var(--border); border-radius: var(--radius-md); overflow: hidden; }
136
+ .sched-cal-header { background: var(--surface2); padding: 6px 4px; text-align: center; font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
137
+ .sched-cal-hour { background: var(--surface); padding: 6px 6px 6px 0; font-size: 10px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; display: flex; align-items: flex-start; justify-content: flex-end; }
138
+ .sched-cal-cell { background: var(--surface); padding: 3px; min-height: 44px; display: flex; flex-direction: column; gap: 2px; }
139
+ .sched-cal-slot { padding: 3px 6px; border-radius: 3px; font-size: 10px; cursor: pointer; border-left: 3px solid; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: filter 0.15s; }
140
+ .sched-cal-slot:hover { filter: brightness(1.2); }
141
+
134
142
  .token-agent-table { width: 100%; margin-top: var(--space-5); }
135
143
  .token-agent-table th { text-align: right; font-size: var(--text-sm); color: var(--muted); font-weight: 500; padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border); white-space: nowrap; }
136
144
  .token-agent-table th:first-child { text-align: left; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.123",
3
+ "version": "0.1.124",
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"