clementine-agent 1.4.1 → 1.4.2

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 +113 -64
  2. package/package.json +1 -1
@@ -8718,13 +8718,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
8718
8718
  .home-rail {
8719
8719
  display: flex;
8720
8720
  flex-direction: column;
8721
- gap: 12px;
8721
+ gap: 8px;
8722
8722
  overflow-y: auto;
8723
8723
  position: relative;
8724
8724
  }
8725
8725
  .home-rail.collapsed {
8726
8726
  display: none;
8727
8727
  }
8728
+ /* Auto-hide cards that have no actionable content (set via JS toggling .rail-card.empty) */
8729
+ .rail-card.empty { display: none; }
8728
8730
  .rail-collapse-btn {
8729
8731
  position: absolute;
8730
8732
  top: -4px;
@@ -8745,23 +8747,24 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
8745
8747
  .rail-card {
8746
8748
  background: var(--bg-card);
8747
8749
  border: 1px solid var(--border);
8748
- border-radius: 10px;
8750
+ border-radius: var(--radius-md);
8749
8751
  overflow: hidden;
8750
- box-shadow: 0 1px 4px rgba(0,0,0,0.04);
8752
+ box-shadow: var(--shadow-xs);
8751
8753
  }
8752
8754
  .rail-header {
8753
8755
  display: flex;
8754
8756
  align-items: center;
8755
8757
  justify-content: space-between;
8756
- padding: 10px 14px;
8757
- font-size: 12px;
8758
+ padding: 8px 12px;
8759
+ font-size: var(--text-xs);
8758
8760
  font-weight: 600;
8759
- color: var(--text-secondary);
8760
- border-bottom: 1px solid var(--border);
8761
- background: var(--bg-secondary);
8761
+ text-transform: uppercase;
8762
+ letter-spacing: 0.04em;
8763
+ color: var(--text-muted);
8764
+ background: transparent;
8762
8765
  }
8763
- .rail-body { padding: 12px 14px; font-size: 12.5px; line-height: 1.5; }
8764
- .rail-body .empty-state, .rail-body .skel-row { font-size: 11px; }
8766
+ .rail-body { padding: 8px 12px 10px; font-size: var(--text-sm); line-height: 1.45; }
8767
+ .rail-body .empty-state, .rail-body .skel-row { font-size: var(--text-xs); }
8765
8768
  .rail-badge {
8766
8769
  display: inline-flex;
8767
8770
  align-items: center;
@@ -10540,35 +10543,39 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
10540
10543
  margin-top: 4px;
10541
10544
  }
10542
10545
 
10543
- /* ── Timeline ───────────────────────────── */
10546
+ /* ── Timeline (compact) ───────────────────── */
10544
10547
  .timeline {
10545
10548
  position: relative;
10546
- padding-left: 24px;
10549
+ padding-left: 18px;
10547
10550
  }
10548
10551
  .timeline::before {
10549
10552
  content: '';
10550
10553
  position: absolute;
10551
- left: 7px;
10554
+ left: 5px;
10552
10555
  top: 4px;
10553
10556
  bottom: 4px;
10554
- width: 2px;
10557
+ width: 1px;
10555
10558
  background: var(--border);
10556
10559
  }
10557
10560
  .timeline-item {
10558
10561
  position: relative;
10559
- padding: 8px 0;
10560
- font-size: 12px;
10562
+ padding: 4px 0;
10563
+ font-size: var(--text-sm);
10561
10564
  display: flex;
10562
- align-items: flex-start;
10563
- gap: 10px;
10565
+ align-items: center;
10566
+ gap: 8px;
10567
+ border-radius: var(--radius-xs);
10568
+ transition: background var(--motion);
10564
10569
  }
10570
+ .timeline-item:hover { background: var(--bg-hover); }
10565
10571
  .timeline-item::before {
10566
10572
  content: '';
10567
10573
  position: absolute;
10568
- left: -20px;
10569
- top: 14px;
10570
- width: 8px;
10571
- height: 8px;
10574
+ left: -16px;
10575
+ top: 50%;
10576
+ transform: translateY(-50%);
10577
+ width: 7px;
10578
+ height: 7px;
10572
10579
  border-radius: 50%;
10573
10580
  background: var(--text-muted);
10574
10581
  border: 2px solid var(--bg-primary);
@@ -10576,8 +10583,32 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
10576
10583
  }
10577
10584
  .timeline-item.ok::before { background: var(--green); }
10578
10585
  .timeline-item.error::before { background: var(--red); }
10579
- .timeline-msg { flex: 1; color: var(--text-primary); line-height: 1.4; }
10580
- .timeline-time { flex-shrink: 0; color: var(--text-muted); font-size: 11px; }
10586
+ .timeline-msg {
10587
+ flex: 1;
10588
+ color: var(--text-primary);
10589
+ line-height: 1.35;
10590
+ white-space: nowrap;
10591
+ overflow: hidden;
10592
+ text-overflow: ellipsis;
10593
+ min-width: 0;
10594
+ }
10595
+ .timeline-title { font-weight: 500; }
10596
+ .timeline-agent {
10597
+ color: var(--clementine);
10598
+ font-size: var(--text-xs);
10599
+ margin-left: 6px;
10600
+ font-weight: 500;
10601
+ }
10602
+ .timeline-body {
10603
+ color: var(--text-muted);
10604
+ font-size: var(--text-xs);
10605
+ margin-left: 4px;
10606
+ }
10607
+ .timeline-time { flex-shrink: 0; color: var(--text-muted); font-size: var(--text-xs); }
10608
+ /* Cap activity card height so chat dominates the page */
10609
+ .home-activity .card-body { max-height: 320px; overflow-y: auto; padding: 10px 16px; }
10610
+ /* Hide chat profile selector when default — the row gets cleaner */
10611
+ .home-chat-input-row .chat-profile-spacer { display: none; }
10581
10612
 
10582
10613
  /* ── Task Cards ─────────────────────────── */
10583
10614
  .task-grid {
@@ -17260,15 +17291,15 @@ var sourceIcons = {
17260
17291
  };
17261
17292
 
17262
17293
  function activityEventHtml(e) {
17263
- var icon = sourceIcons[e.source] || '●';
17264
17294
  var statusCls = e.status === 'ok' || e.status === 'approved' ? 'ok'
17265
17295
  : (e.status === 'error' || e.eventType === 'cron_error') ? 'error'
17266
- : e.status === 'pending' ? '' : '';
17267
- var agentLabel = e.agentSlug ? '<span style="color:var(--accent);font-size:11px;margin-left:4px">[' + esc(e.agentSlug) + ']</span>' : '';
17268
- return '<div class="timeline-item ' + statusCls + '">'
17269
- + '<span style="margin-right:6px">' + icon + '</span>'
17270
- + '<span class="timeline-msg">' + esc(e.title) + agentLabel
17271
- + (e.body ? '<span onclick="this.style.whiteSpace=this.style.whiteSpace===\\x27normal\\x27?\\x27nowrap\\x27:\\x27normal\\x27;this.style.overflow=this.style.whiteSpace===\\x27normal\\x27?\\x27visible\\x27:\\x27hidden\\x27;this.style.maxWidth=this.style.whiteSpace===\\x27normal\\x27?\\x27none\\x27:\\x27400px\\x27" style="display:block;font-size:11px;color:var(--text-muted);margin-top:2px;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer" title="Click to expand">' + esc(e.body) + '</span>' : '')
17296
+ : e.status === 'pending' ? 'pending' : '';
17297
+ var agentLabel = e.agentSlug ? '<span class="timeline-agent">[' + esc(e.agentSlug) + ']</span>' : '';
17298
+ return '<div class="timeline-item ' + statusCls + '" title="' + esc(e.body || e.title) + '">'
17299
+ + '<span class="timeline-msg">'
17300
+ + '<span class="timeline-title">' + esc(e.title) + '</span>'
17301
+ + agentLabel
17302
+ + (e.body ? ' <span class="timeline-body">&middot; ' + esc(e.body) + '</span>' : '')
17272
17303
  + '</span>'
17273
17304
  + '<span class="timeline-time">' + timeAgo(e.timestamp) + '</span>'
17274
17305
  + '</div>';
@@ -18396,13 +18427,17 @@ async function loadProfiles() {
18396
18427
  var d = await r.json();
18397
18428
  var sel = document.getElementById('chat-profile-select');
18398
18429
  sel.innerHTML = '<option value="">Default</option>';
18430
+ var customCount = 0;
18399
18431
  for (var p of (d.profiles || [])) {
18400
18432
  var opt = document.createElement('option');
18401
18433
  opt.value = p.slug;
18402
18434
  opt.textContent = p.name + (p.description ? ' — ' + p.description : '');
18403
18435
  if (p.slug === d.active) opt.selected = true;
18404
18436
  sel.appendChild(opt);
18437
+ customCount++;
18405
18438
  }
18439
+ // Hide the picker entirely if there are no custom profiles — declutters the chat input row.
18440
+ sel.style.display = customCount === 0 ? 'none' : '';
18406
18441
  } catch(e) { /* profiles are optional */ }
18407
18442
  }
18408
18443
 
@@ -20561,44 +20596,53 @@ function toggleHomeRail() {
20561
20596
  }
20562
20597
  }
20563
20598
 
20599
+ function _railCard(bodyId) {
20600
+ var body = document.getElementById(bodyId);
20601
+ return body ? body.closest('.rail-card') : null;
20602
+ }
20603
+ function _setRailEmpty(bodyId, isEmpty) {
20604
+ var card = _railCard(bodyId);
20605
+ if (card) card.classList.toggle('empty', !!isEmpty);
20606
+ }
20607
+
20564
20608
  async function refreshHomeRail() {
20565
- // Daemon status
20609
+ // Daemon status — only surface when explicitly stopped. Treat null/undefined
20610
+ // (running-state unknown) as "fine, hide" since the dashboard wouldn't be
20611
+ // serving requests if the daemon were truly down.
20566
20612
  try {
20567
20613
  var rs = await apiFetch('/api/status');
20568
20614
  var ds = await rs.json();
20615
+ var stopped = ds.running === false;
20569
20616
  var pip = document.querySelector('#rail-daemon-body .agent-activity-dot');
20570
20617
  var label = document.querySelector('#rail-daemon-body .agent-activity span:last-child');
20571
- if (label) label.textContent = ds.running ? 'Daemon running' : 'Daemon stopped';
20572
- if (pip) pip.style.background = ds.running ? '#22c55e' : '#ef4444';
20618
+ if (label) label.textContent = stopped ? 'Daemon stopped' : 'Running';
20619
+ if (pip) pip.style.background = stopped ? '#ef4444' : '#22c55e';
20573
20620
  var up = document.getElementById('rail-daemon-uptime');
20574
20621
  if (up && ds.uptimeMs) up.textContent = Math.round(ds.uptimeMs / 60000) + 'm';
20575
- } catch { /* */ }
20622
+ _setRailEmpty('rail-daemon-body', !stopped);
20623
+ } catch { _setRailEmpty('rail-daemon-body', true); }
20576
20624
 
20577
- // Today's plan (compact)
20625
+ // Today's plan (compact). Hide card if no plan or zero items.
20578
20626
  try {
20579
20627
  var rp = await apiFetch('/api/daily-plan');
20580
20628
  var dp = await rp.json();
20581
20629
  var planEl = document.getElementById('home-plan-content');
20630
+ var items = dp && dp.plan ? (dp.plan.items || []) : [];
20582
20631
  if (planEl) {
20583
- if (!dp || !dp.plan) {
20584
- planEl.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">No plan yet today.</div>';
20632
+ if (items.length === 0) {
20633
+ planEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan yet today.</div>';
20585
20634
  } else {
20586
- var items = (dp.plan.items || []).slice(0, 4);
20587
- if (items.length === 0) {
20588
- planEl.innerHTML = '<div style="font-size:12px;color:var(--text-muted)">No items in today\\x27s plan.</div>';
20589
- } else {
20590
- planEl.innerHTML = items.map(function(it) {
20591
- return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
20592
- }).join('');
20593
- }
20635
+ planEl.innerHTML = items.slice(0, 4).map(function(it) {
20636
+ return '<div class="rail-row"><span class="label">' + esc(it.title || it.text || '') + '</span><span class="meta">' + esc(it.time || '') + '</span></div>';
20637
+ }).join('');
20594
20638
  }
20595
20639
  }
20640
+ _setRailEmpty('home-plan-content', items.length === 0);
20596
20641
  } catch {
20597
- var pe = document.getElementById('home-plan-content');
20598
- if (pe) pe.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">No plan available.</div>';
20642
+ _setRailEmpty('home-plan-content', true);
20599
20643
  }
20600
20644
 
20601
- // Upcoming cron fires (next 3)
20645
+ // Upcoming cron fires (next 3) — hide card if nothing scheduled
20602
20646
  try {
20603
20647
  var rc = await apiFetch('/api/cron');
20604
20648
  var dc = await rc.json();
@@ -20609,13 +20653,14 @@ async function refreshHomeRail() {
20609
20653
  var uc = document.getElementById('rail-upcoming-count');
20610
20654
  if (uc) uc.textContent = String(jobs.length);
20611
20655
  if (ue) {
20612
- ue.innerHTML = top.length ? top.map(function(j) {
20656
+ ue.innerHTML = top.map(function(j) {
20613
20657
  return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27build\\x27,{tab:\\x27crons\\x27})"><span class="label">' + esc(j.name) + '</span><span class="meta">' + esc(timeUntil(j.nextRun)) + '</span></div>';
20614
- }).join('') : '<div style="font-size:11px;color:var(--text-muted)">Nothing scheduled soon.</div>';
20658
+ }).join('');
20615
20659
  }
20616
- } catch { /* */ }
20660
+ _setRailEmpty('rail-upcoming', top.length === 0);
20661
+ } catch { _setRailEmpty('rail-upcoming', true); }
20617
20662
 
20618
- // Active unleashed runs
20663
+ // Active unleashed runs — hide card unless something running
20619
20664
  try {
20620
20665
  var ru = await apiFetch('/api/unleashed');
20621
20666
  var du = await ru.json();
@@ -20627,25 +20672,27 @@ async function refreshHomeRail() {
20627
20672
  else ac.style.display = 'none';
20628
20673
  }
20629
20674
  if (ae) {
20630
- ae.innerHTML = active.length ? active.map(function(t) {
20675
+ ae.innerHTML = active.map(function(t) {
20631
20676
  return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27build\\x27,{tab:\\x27workflows\\x27})"><span class="label">' + esc(t.name) + '</span><span class="meta">' + esc(t.phase || '') + '</span></div>';
20632
- }).join('') : '<div style="font-size:11px;color:var(--text-muted)">Nothing running.</div>';
20677
+ }).join('');
20633
20678
  }
20634
- } catch { /* */ }
20679
+ _setRailEmpty('rail-active', active.length === 0);
20680
+ } catch { _setRailEmpty('rail-active', true); }
20635
20681
 
20636
- // Time saved (rough: cron runs * 5min + activity exchanges * 2min, this week)
20682
+ // Time saved (compact). Hide if zero.
20637
20683
  try {
20638
20684
  var rm = await apiFetch('/api/metrics?period=week');
20639
20685
  var dm = await rm.json();
20640
20686
  var minutes = ((dm.cronRuns || 0) * 5) + ((dm.exchanges || 0) * 2);
20641
20687
  var ts = document.getElementById('rail-time-saved');
20642
20688
  if (ts) {
20643
- if (minutes >= 60) ts.innerHTML = '<div style="font-size:18px;font-weight:600">' + (minutes / 60).toFixed(1) + 'h</div><div style="font-size:11px;color:var(--text-muted)">across ' + (dm.cronRuns || 0) + ' cron runs + ' + (dm.exchanges || 0) + ' chats</div>';
20644
- else ts.innerHTML = '<div style="font-size:18px;font-weight:600">' + minutes + 'm</div><div style="font-size:11px;color:var(--text-muted)">across ' + (dm.cronRuns || 0) + ' cron runs</div>';
20689
+ if (minutes >= 60) ts.innerHTML = '<div style="font-size:var(--text-md);font-weight:600">' + (minutes / 60).toFixed(1) + 'h</div><div style="font-size:11px;color:var(--text-muted)">' + (dm.cronRuns || 0) + ' runs · ' + (dm.exchanges || 0) + ' chats</div>';
20690
+ else ts.innerHTML = '<div style="font-size:var(--text-md);font-weight:600">' + minutes + 'm</div><div style="font-size:11px;color:var(--text-muted)">' + (dm.cronRuns || 0) + ' runs</div>';
20645
20691
  }
20646
- } catch { /* */ }
20692
+ _setRailEmpty('rail-time-saved', minutes === 0);
20693
+ } catch { _setRailEmpty('rail-time-saved', true); }
20647
20694
 
20648
- // Approvals (self-improve proposals + pending skills)
20695
+ // Approvals hide card unless something pending
20649
20696
  try {
20650
20697
  var rsi = await apiFetch('/api/self-improve');
20651
20698
  var dsi = await rsi.json();
@@ -20657,12 +20704,12 @@ async function refreshHomeRail() {
20657
20704
  else ac2.style.display = 'none';
20658
20705
  }
20659
20706
  if (ae2) {
20660
- if (pending.length === 0) ae2.innerHTML = '<div style="font-size:11px;color:var(--text-muted)">Nothing pending.</div>';
20661
- else ae2.innerHTML = pending.slice(0, 3).map(function(p) {
20707
+ ae2.innerHTML = pending.slice(0, 3).map(function(p) {
20662
20708
  return '<div class="rail-row clickable-row" onclick="navigateTo(\\x27brain\\x27,{tab:\\x27learning\\x27})"><span class="label">' + esc(p.area || 'proposal') + ': ' + esc((p.target || '').slice(0, 40)) + '</span><span class="meta">' + esc(((p.score || 0) * 100).toFixed(0)) + '%</span></div>';
20663
20709
  }).join('');
20664
20710
  }
20665
- } catch { /* */ }
20711
+ _setRailEmpty('rail-approvals', pending.length === 0);
20712
+ } catch { _setRailEmpty('rail-approvals', true); }
20666
20713
  }
20667
20714
 
20668
20715
  function timeUntil(iso) {
@@ -23992,6 +24039,8 @@ async function refreshSalesforce() {
23992
24039
  if (d.status) { try { refreshStatus(d.status); } catch(e) { console.warn('init: status', e); } }
23993
24040
  if (d.activity) { try { refreshActivity(false, d.activity); } catch(e) { console.warn('init: activity', e); } }
23994
24041
  else { try { refreshActivity(); } catch(e) { console.warn('init: activity fallback', e); } }
24042
+ // Populate the home right rail (daemon, plan, runs, time-saved, approvals)
24043
+ if (typeof refreshHomeRail === 'function') { try { refreshHomeRail(); } catch(e) { console.warn('init: rail', e); } }
23995
24044
  if (d.office) { try { refreshTeamNav(d.office); refreshTeamPulse(d.office); } catch(e) { console.warn('init: office', e); } }
23996
24045
  if (d.plan) { try { refreshHomePlan(d.plan); } catch(e) { console.warn('init: plan', e); } }
23997
24046
  if (d.version) { try { _loadedHash = d.version.started; } catch(e) { /* ignore */ } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",