clementine-agent 1.18.110 → 1.18.111

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.
Files changed (2) hide show
  1. package/dist/cli/dashboard.js +124 -21
  2. package/package.json +1 -1
@@ -24459,9 +24459,17 @@ function operationUsageBadge(usage) {
24459
24459
  return '<span class="badge badge-blue" title="' + esc(formatTokens(usage.totalInput || 0)) + ' input, ' + esc(formatTokens(usage.totalOutput || 0)) + ' output">' + esc(formatTokens(usage.totalTokens || 0)) + ' tok 7d</span>';
24460
24460
  }
24461
24461
 
24462
+ // PRD §pretty-cron / 1.18.111: pretty primary, raw cron in a hover
24463
+ // tooltip. Power users still get the literal expression on hover; casual
24464
+ // users never have to read "0 8-18 * * 1-5". Falls back to raw inline
24465
+ // (with a help-cursor hint) when describeCron can't summarize.
24462
24466
  function operationScheduleHtml(schedule) {
24463
- var desc = describeCron(schedule || '');
24464
- return desc ? esc(desc) + ' <code>' + esc(schedule) + '</code>' : '<code style="color:var(--accent)">' + esc(schedule || '') + '</code>';
24467
+ var raw = schedule || '';
24468
+ var desc = describeCron(raw);
24469
+ if (desc) {
24470
+ return '<span title="' + esc(raw) + '" style="cursor:help">' + esc(desc) + '</span>';
24471
+ }
24472
+ return '<code title="' + esc(raw) + '" style="color:var(--accent);cursor:help">' + esc(raw) + '</code>';
24465
24473
  }
24466
24474
 
24467
24475
  function operationSectionHeader(title, subtitle, badgeClass, badgeText, marginTop) {
@@ -29577,44 +29585,139 @@ function updateScheduleHint() {
29577
29585
 
29578
29586
  const monthNames = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
29579
29587
 
29588
+ // PRD §pretty-cron / 1.18.111 — describe a cron expression in casual
29589
+ // human-readable form. Output style:
29590
+ // - "Mondays at 8 AM" not "Every Monday at 8:00 AM"
29591
+ // - "5 PM" not "5:00 PM" when minutes=0
29592
+ // - "8 AM–6 PM" (en-dash range) for hour ranges
29593
+ // - "Every minute" / "Every 2 minutes" / "Every 4 hours"
29594
+ // - "Hourly weekdays 8 AM–6 PM" for the business-hours pattern
29595
+ // - Falls back to '' when the expression is too exotic to summarize;
29596
+ // the renderer then shows the raw cron only as a last resort.
29580
29597
  function describeCron(expr) {
29581
- const parts = expr.split(/\\s+/);
29598
+ if (!expr || typeof expr !== 'string') return '';
29599
+ // @aliases first — common shortcuts.
29600
+ var aliases = {
29601
+ '@yearly': 'Once a year (Jan 1, midnight)',
29602
+ '@annually': 'Once a year (Jan 1, midnight)',
29603
+ '@monthly': 'First of every month, midnight',
29604
+ '@weekly': 'Sundays at midnight',
29605
+ '@daily': 'Every day at midnight',
29606
+ '@hourly': 'Every hour',
29607
+ '@reboot': 'On daemon start',
29608
+ };
29609
+ if (aliases[expr]) return aliases[expr];
29610
+
29611
+ var parts = expr.trim().split(/\\s+/);
29582
29612
  if (parts.length !== 5) return '';
29583
- const [min, hour, dom, month, dow] = parts;
29613
+ var min = parts[0], hour = parts[1], dom = parts[2], month = parts[3], dow = parts[4];
29584
29614
 
29585
- // Every N minutes
29586
- if (min.startsWith('*/')) return 'Every ' + min.slice(2) + ' minutes';
29587
- // Every N hours
29588
- if (hour.startsWith('*/')) return 'Every ' + hour.slice(2) + ' hours';
29615
+ // ── Sub-hour cadence ────────────────────────────────────────────────
29616
+ if (min === '*' && hour === '*' && dom === '*' && month === '*' && dow === '*') return 'Every minute';
29617
+ if (min.startsWith('*/')) {
29618
+ var n = parseInt(min.slice(2), 10);
29619
+ if (Number.isFinite(n)) return n === 1 ? 'Every minute' : 'Every ' + n + ' minutes';
29620
+ }
29621
+ // Every N hours (e.g. "0 */2 * * *")
29622
+ if (hour.startsWith('*/') && (min === '0' || min === '*')) {
29623
+ var nh = parseInt(hour.slice(2), 10);
29624
+ if (Number.isFinite(nh)) return nh === 1 ? 'Every hour' : 'Every ' + nh + ' hours';
29625
+ }
29589
29626
 
29590
- const time = formatTime(+hour, +min);
29627
+ // ── Hour ranges (e.g. "0 8-18 * * 1-5" — hourly during business hours) ──
29628
+ var rangeMatch = /^(\d{1,2})-(\d{1,2})$/.exec(hour);
29629
+ if (rangeMatch && (min === '0' || min === '*')) {
29630
+ var startH = parseInt(rangeMatch[1], 10);
29631
+ var endH = parseInt(rangeMatch[2], 10);
29632
+ if (Number.isFinite(startH) && Number.isFinite(endH)) {
29633
+ var span = formatHour(startH) + '–' + formatHour(endH);
29634
+ if (dow === '1-5') return 'Hourly weekdays ' + span;
29635
+ if (dow === '*' && dom === '*' && month === '*') return 'Hourly ' + span;
29636
+ if (/^[0-6]$/.test(dow)) return 'Hourly ' + plural(dayNames[+dow]) + ' ' + span;
29637
+ }
29638
+ }
29639
+
29640
+ // ── Comma-list of hours at fixed minute (e.g. "0 8,12,16 * * *") ────
29641
+ // Check this BEFORE parseInt — parseInt is lenient and would parse
29642
+ // "8,12,16" as 8, missing the multi-hour case entirely.
29643
+ if (hour.indexOf(',') !== -1 && min !== '*' && !min.startsWith('*/')) {
29644
+ var minN = parseInt(min, 10);
29645
+ if (Number.isFinite(minN)) {
29646
+ return 'Daily at ' + hour.split(',').map(function(h) { return formatTimePretty(+h, minN); }).join(', ');
29647
+ }
29648
+ }
29649
+
29650
+ // ── Single-time-of-day patterns ─────────────────────────────────────
29651
+ var hourNum = parseInt(hour, 10);
29652
+ var minNum = parseInt(min, 10);
29653
+ if (!Number.isFinite(hourNum) || !Number.isFinite(minNum)) return '';
29654
+ // Reject parseInt's lenient mode: a hour like "8,12" would parse to 8.
29655
+ if (String(hourNum) !== hour || String(minNum) !== min) return '';
29656
+ var time = formatTimePretty(hourNum, minNum);
29591
29657
 
29592
29658
  // Specific date: day + month set (e.g. "10 16 1 3 *" = Mar 1 at 4:10 PM)
29593
29659
  if (dom !== '*' && month !== '*') {
29594
- const monthStr = monthNames[+month] || 'Month ' + month;
29660
+ var monthStr = monthNames[+month] || ('Month ' + month);
29595
29661
  return monthStr + ' ' + dom + ' at ' + time;
29596
29662
  }
29597
29663
 
29598
29664
  // Day of month only (e.g. "0 9 15 * *" = 15th of every month)
29599
29665
  if (dom !== '*' && month === '*' && dow === '*') {
29600
- const suffix = +dom === 1 ? 'st' : +dom === 2 ? 'nd' : +dom === 3 ? 'rd' : 'th';
29601
- return dom + suffix + ' of every month at ' + time;
29666
+ return ordinal(+dom) + ' of every month at ' + time;
29667
+ }
29668
+
29669
+ // Weekdays (Mon-Fri)
29670
+ if (dow === '1-5') return 'Weekdays at ' + time;
29671
+
29672
+ // Specific weekday → pluralize ("Mondays at 8 AM" not "Every Monday at 8 AM")
29673
+ if (/^[0-6]$/.test(dow)) return plural(dayNames[+dow]) + ' at ' + time;
29674
+
29675
+ // Multiple specific weekdays (e.g. "0 9 * * 1,3,5")
29676
+ if (/^[0-6](,[0-6])+$/.test(dow)) {
29677
+ return dow.split(',').map(function(d) { return shortDay(+d); }).join(', ') + ' at ' + time;
29602
29678
  }
29603
29679
 
29604
- // Weekdays
29605
- if (dow === '1-5' && !hour.includes(',')) return 'Weekdays at ' + time;
29606
29680
  // Every day
29607
- if (dow === '*' && dom === '*' && month === '*' && !hour.includes(',') && !hour.includes('/')) return 'Every day at ' + time;
29608
- // Specific weekday
29609
- if (/^[0-6]$/.test(dow) && !hour.includes(',')) return 'Every ' + dayNames[+dow] + ' at ' + time;
29610
- // Multiple weekdays (e.g. "0 9 * * 1,3,5")
29611
- if (/^[0-6](,[0-6])+$/.test(dow)) return dow.split(',').map(d => dayNames[+d]).join(', ') + ' at ' + time;
29612
- // Multiple hours
29613
- if (hour.includes(',')) return 'Daily at ' + hour.split(',').map(h => formatTime(+h, +min)).join(', ');
29681
+ if (dow === '*' && dom === '*' && month === '*') return 'Every day at ' + time;
29614
29682
 
29615
29683
  return '';
29616
29684
  }
29617
29685
 
29686
+ function plural(day) {
29687
+ // "Monday" → "Mondays". Days end in y already so just append 's'.
29688
+ return day + 's';
29689
+ }
29690
+
29691
+ function shortDay(d) {
29692
+ // Compact form for multi-day lists: "Mon, Wed, Fri".
29693
+ return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d] || ('day ' + d);
29694
+ }
29695
+
29696
+ function ordinal(n) {
29697
+ // 1st / 2nd / 3rd / 4th… for the day-of-month phrasing.
29698
+ if (n >= 11 && n <= 13) return n + 'th';
29699
+ var lastDigit = n % 10;
29700
+ if (lastDigit === 1) return n + 'st';
29701
+ if (lastDigit === 2) return n + 'nd';
29702
+ if (lastDigit === 3) return n + 'rd';
29703
+ return n + 'th';
29704
+ }
29705
+
29706
+ function formatHour(h) {
29707
+ // Compact hour-only format used in ranges: "8 AM" / "12 PM" / "6 PM".
29708
+ var ampm = h >= 12 ? 'PM' : 'AM';
29709
+ var hr = h === 0 ? 12 : (h > 12 ? h - 12 : h);
29710
+ return hr + ' ' + ampm;
29711
+ }
29712
+
29713
+ function formatTimePretty(h, m) {
29714
+ // "8 AM" when minutes=0, "8:30 AM" otherwise. Casual, no leading zeros.
29715
+ if (m === 0) return formatHour(h);
29716
+ var ampm = h >= 12 ? 'PM' : 'AM';
29717
+ var hr = h === 0 ? 12 : (h > 12 ? h - 12 : h);
29718
+ return hr + ':' + String(m).padStart(2, '0') + ' ' + ampm;
29719
+ }
29720
+
29618
29721
  function setScheduleFromCron(expr) {
29619
29722
  // Try to reverse-map a cron expression back to the builder
29620
29723
  const parts = expr.split(/\\s+/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.110",
3
+ "version": "1.18.111",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",