@yemi33/minions 0.1.1730 → 0.1.1732

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1732 (2026-05-05)
4
+
5
+ ### Features
6
+ - fix pipeline cron display (#2087)
7
+
3
8
  ## 0.1.1730 (2026-05-05)
4
9
 
5
10
  ### Features
@@ -268,6 +268,13 @@ function _buildNodeChain(stages, run, options) {
268
268
  return html;
269
269
  }
270
270
 
271
+ function _renderPipelineTriggerLabel(cron) {
272
+ if (!cron) return 'Manual trigger';
273
+ var human = _cronToHuman(cron);
274
+ var tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
275
+ return escHtml(human) + ' <span style="opacity:0.6">(' + escHtml(tz) + ')</span>';
276
+ }
277
+
271
278
  function renderPipelines(pipelines) {
272
279
  pipelines = (pipelines || []).filter(function(p) { return !isDeleted('pipeline:' + p.id); });
273
280
  _pipelinesData = pipelines;
@@ -331,7 +338,7 @@ function openPipelineDetail(id) {
331
338
  // Status + actions
332
339
  var activeRun = _getPipelineActiveRun(p);
333
340
  html += '<div style="display:flex;justify-content:space-between;align-items:center">' +
334
- '<span style="font-size:10px;color:var(--muted)">' + (p.trigger?.cron ? escHtml(_cronToHuman(p.trigger.cron)) + ' <span style="opacity:0.6">(' + escHtml(p.trigger.cron) + ', ' + escHtml(Intl.DateTimeFormat().resolvedOptions().timeZone) + ')</span>' : 'Manual trigger') + ' · ' + escHtml(_getPipelineStageLabel(p)) + '</span>' +
341
+ '<span style="font-size:10px;color:var(--muted)">' + _renderPipelineTriggerLabel(p.trigger?.cron) + ' · ' + escHtml(_getPipelineStageLabel(p)) + '</span>' +
335
342
  '<div style="display:flex;gap:6px">' +
336
343
  (activeRun
337
344
  ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_abortPipeline(\'' + escHtml(id) + '\',this)">Abort</button>' +
@@ -5,14 +5,50 @@
5
5
  const _DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
6
6
  const _DAY_NAMES = ['Sundays', 'Mondays', 'Tuesdays', 'Wednesdays', 'Thursdays', 'Fridays', 'Saturdays'];
7
7
 
8
- /** Convert 3-field cron (minute hour dayOfWeek) to human-readable text */
8
+ function _formatCronDowList(days, timeStr, fallback) {
9
+ const normalized = [...new Set(days)].sort((a, b) => a - b);
10
+ const key = normalized.join(',');
11
+ if (key === '0,1,2,3,4,5,6') return 'Daily at ' + timeStr;
12
+ if (key === '1,2,3,4,5') return 'Weekdays at ' + timeStr;
13
+ if (key === '0,6') return 'Weekends at ' + timeStr;
14
+ if (normalized.length === 1) return _DAY_NAMES[normalized[0]] + ' at ' + timeStr;
15
+ if (normalized.length > 1) return normalized.map(d => _DAY_NAMES[d]).join(', ') + ' at ' + timeStr;
16
+ return fallback;
17
+ }
18
+
19
+ function _parseCronDowList(dow, allowSevenAsSunday) {
20
+ if (!dow || typeof dow !== 'string') return null;
21
+ const values = [];
22
+ const parts = dow.split(',');
23
+ for (const part of parts) {
24
+ const token = part.trim();
25
+ if (!/^\d+$/.test(token)) return null;
26
+ let day = parseInt(token, 10);
27
+ if (allowSevenAsSunday && day === 7) day = 0;
28
+ if (day < 0 || day > 6) return null;
29
+ values.push(day);
30
+ }
31
+ return values;
32
+ }
33
+
34
+ function _cronTimeString(minute, hour) {
35
+ const h = parseInt(hour, 10);
36
+ const m = parseInt(minute, 10);
37
+ if (isNaN(h) || isNaN(m)) return null;
38
+ return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');
39
+ }
40
+
41
+ /** Convert 3-field Minions cron or common 5-field cron to human-readable text */
9
42
  function _cronToHuman(cron) {
10
43
  if (!cron || typeof cron !== 'string') return cron || '';
11
44
  const parts = cron.trim().split(/\s+/);
12
- if (parts.length !== 3) return cron;
13
- const [minute, hour, dow] = parts;
45
+ if (parts.length !== 3 && parts.length !== 5) return cron;
46
+ const minute = parts[0];
47
+ const hour = parts[1];
48
+ const dow = parts.length === 3 ? parts[2] : parts[4];
14
49
 
15
- if (minute === '*' && hour === '*' && dow === '*') return 'Every minute';
50
+ if (parts.length === 5 && (parts[2] !== '*' || parts[3] !== '*')) return cron;
51
+ if (parts.length === 3 && minute === '*' && hour === '*' && dow === '*') return 'Every minute';
16
52
 
17
53
  const h = parseInt(hour, 10);
18
54
  const m = parseInt(minute, 10);
@@ -26,13 +62,20 @@ function _cronToHuman(cron) {
26
62
  const normalized = dow.split(',').map(d => d.trim()).sort().join(',');
27
63
  if (dow === '1-5' || normalized === '1,2,3,4,5') return 'Weekdays at ' + timeStr;
28
64
  if (normalized === '0,6' || normalized === '6,0') return 'Weekends at ' + timeStr;
65
+ if (parts.length === 5 && (dow === '0-6' || dow === '0-7')) return 'Daily at ' + timeStr;
29
66
 
30
67
  // Single day
31
- const dayNum = parseInt(dow, 10);
32
- if (!isNaN(dayNum) && dayNum >= 0 && dayNum <= 6 && String(dayNum) === dow) {
68
+ let dayNum = parseInt(dow, 10);
69
+ if (parts.length === 5 && dayNum === 7 && dow === '7') dayNum = 0;
70
+ if (!isNaN(dayNum) && dayNum >= 0 && dayNum <= 6 && String(parseInt(dow, 10)) === dow) {
33
71
  return _DAY_NAMES[dayNum] + ' at ' + timeStr;
34
72
  }
35
73
 
74
+ if (parts.length === 5) {
75
+ const days = _parseCronDowList(dow, true);
76
+ if (days) return _formatCronDowList(days, timeStr, cron);
77
+ }
78
+
36
79
  return cron;
37
80
  }
38
81
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-05T09:50:02.177Z"
4
+ "cachedAt": "2026-05-05T09:53:42.976Z"
5
5
  }
@@ -245,23 +245,33 @@ function execGit(execFileSync, targetDir, args, timeout = 5000) {
245
245
  })).trim();
246
246
  }
247
247
 
248
+ function parseOriginHeadBranch(headRef) {
249
+ const branch = String(headRef || '').trim().replace(/^refs\/remotes\/origin\//, '');
250
+ return branch && branch !== 'HEAD' ? branch : '';
251
+ }
252
+
253
+ function discoverMainBranch(execFileSync, targetDir) {
254
+ try {
255
+ return parseOriginHeadBranch(execGit(execFileSync, targetDir, ['symbolic-ref', 'refs/remotes/origin/HEAD']));
256
+ } catch {}
257
+
258
+ try {
259
+ execGit(execFileSync, targetDir, ['remote', 'set-head', 'origin', '-a'], 10000);
260
+ return parseOriginHeadBranch(execGit(execFileSync, targetDir, ['symbolic-ref', 'refs/remotes/origin/HEAD']));
261
+ } catch {}
262
+
263
+ return '';
264
+ }
265
+
248
266
  function discoverProjectMetadata(targetDir, options = {}) {
249
267
  const execFileSync = options.execFileSync || defaultExecFileSync;
250
268
  const result = { _found: [] };
251
269
 
252
- try {
253
- let head = '';
254
- try {
255
- head = execGit(execFileSync, targetDir, ['symbolic-ref', 'refs/remotes/origin/HEAD']);
256
- } catch {
257
- head = execGit(execFileSync, targetDir, ['symbolic-ref', 'HEAD']);
258
- }
259
- const branch = head.replace('refs/remotes/origin/', '').replace('refs/heads/', '');
260
- if (branch) {
261
- result.mainBranch = branch;
262
- result._found.push('main branch');
263
- }
264
- } catch {}
270
+ const branch = discoverMainBranch(execFileSync, targetDir);
271
+ if (branch) {
272
+ result.mainBranch = branch;
273
+ result._found.push('main branch');
274
+ }
265
275
 
266
276
  try {
267
277
  const remoteUrl = execGit(execFileSync, targetDir, ['remote', 'get-url', 'origin']);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1730",
3
+ "version": "0.1.1732",
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"