lobsterboard 0.7.0 → 0.8.1

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 (41) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/app.html +32 -2
  3. package/css/builder.css +42 -0
  4. package/dist/lobsterboard.css +1 -1
  5. package/dist/lobsterboard.esm.js +143 -1
  6. package/dist/lobsterboard.esm.js.map +1 -1
  7. package/dist/lobsterboard.esm.min.js +2 -2
  8. package/dist/lobsterboard.esm.min.js.map +1 -1
  9. package/dist/lobsterboard.umd.js +143 -1
  10. package/dist/lobsterboard.umd.js.map +1 -1
  11. package/dist/lobsterboard.umd.min.js +2 -2
  12. package/dist/lobsterboard.umd.min.js.map +1 -1
  13. package/js/builder.js +35 -3506
  14. package/js/editor/auth.js +328 -0
  15. package/js/editor/canvas.js +118 -0
  16. package/js/editor/config.js +101 -0
  17. package/js/editor/controls.js +120 -0
  18. package/js/editor/drag-drop.js +110 -0
  19. package/js/editor/export.js +153 -0
  20. package/js/editor/index.js +20 -0
  21. package/js/editor/modals.js +55 -0
  22. package/js/editor/properties.js +745 -0
  23. package/js/editor/servers.js +154 -0
  24. package/js/editor/state.js +73 -0
  25. package/js/widgets/ai-tools.js +818 -0
  26. package/js/widgets/layout.js +164 -0
  27. package/js/widgets/media.js +505 -0
  28. package/js/widgets/misc.js +501 -0
  29. package/js/widgets/productivity.js +460 -0
  30. package/js/widgets/releases.js +265 -0
  31. package/js/widgets/shared/helpers.js +32 -0
  32. package/js/widgets/shared/icons.js +123 -0
  33. package/js/widgets/shared/stats.js +172 -0
  34. package/js/widgets/system.js +413 -0
  35. package/js/widgets/time.js +318 -0
  36. package/js/widgets/weather.js +154 -0
  37. package/js/widgets/widgets.js +58 -0
  38. package/js/widgets.js +267 -0
  39. package/package.json +8 -2
  40. package/server.cjs +76 -3385
  41. package/src/widgets.js +142 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0] - 2026-03-24
4
+
5
+ ### Added
6
+ - **Claude Usage widget** — Real-time Claude Code subscription monitoring with 5-hour session, 7-day weekly, Opus/Sonnet limits, and extra usage tracking — thanks @JamesTsetsekas!
7
+ - **Claude Usage standalone page** — Full-page dashboard for detailed usage analysis at `/pages/claude-usage/`
8
+
9
+ ### Fixed
10
+ - **OpenClaw version detection** — Widget now correctly parses `openclaw --version` output to fix false "Update available" notifications — thanks @JamesTsetsekas!
11
+
12
+ ### Technical
13
+ - Auto-refreshes expired Claude OAuth tokens via CLI
14
+ - Reads from standard `~/.claude/.credentials.json` location
15
+ - Comprehensive error handling for rate limits and authentication issues
16
+
3
17
  ## [0.7.0] - 2026-03-17
4
18
 
5
19
  ### Added
package/app.html CHANGED
@@ -164,6 +164,11 @@
164
164
  <span class="widget-name">Uptime Monitor</span>
165
165
  <span class="widget-verified" title="Tested & Verified">✓</span>
166
166
  </div>
167
+ <div class="widget-item" draggable="true" data-widget="system-graphical">
168
+ <span class="widget-icon">💻</span>
169
+ <span class="widget-name">System (Graphical)</span>
170
+ <span class="widget-verified" title="Tested & Verified">✓</span>
171
+ </div>
167
172
  </div>
168
173
  </div>
169
174
 
@@ -742,8 +747,33 @@
742
747
  </div>
743
748
  </div>
744
749
 
745
- <script src="js/widgets.js"></script>
746
- <script src="js/builder.js"></script>
750
+ <!-- Widget modules (shared first, then index, then categories) -->
751
+ <script src="js/widgets/shared/helpers.js"></script>
752
+ <script src="js/widgets/shared/icons.js"></script>
753
+ <script src="js/widgets/shared/stats.js"></script>
754
+ <script src="js/widgets/index.js"></script>
755
+ <script src="js/widgets/weather.js"></script>
756
+ <script src="js/widgets/system.js"></script>
757
+ <script src="js/widgets/ai-tools.js"></script>
758
+ <script src="js/widgets/media.js"></script>
759
+ <script src="js/widgets/productivity.js"></script>
760
+ <script src="js/widgets/time.js"></script>
761
+ <script src="js/widgets/layout.js"></script>
762
+ <script src="js/widgets/releases.js"></script>
763
+ <script src="js/widgets/misc.js"></script>
764
+ <!-- Editor modules (order matters: state first, index last) -->
765
+ <script src="js/editor/state.js"></script>
766
+ <script src="js/editor/canvas.js"></script>
767
+ <script src="js/editor/auth.js"></script>
768
+ <script src="js/editor/config.js"></script>
769
+ <script src="js/editor/servers.js"></script>
770
+ <script src="js/editor/drag-drop.js"></script>
771
+ <script src="js/editor/widgets.js"></script>
772
+ <script src="js/editor/properties.js"></script>
773
+ <script src="js/editor/controls.js"></script>
774
+ <script src="js/editor/export.js"></script>
775
+ <script src="js/editor/modals.js"></script>
776
+ <script src="js/editor/index.js"></script>
747
777
  <script>
748
778
  // Load page links into nav
749
779
  fetch('/api/pages').then(r => r.json()).then(pages => {
package/css/builder.css CHANGED
@@ -535,6 +535,48 @@ body {
535
535
  color: var(--text-primary);
536
536
  }
537
537
 
538
+ /* System Graphical Widget */
539
+ .placed-widget .system-metric {
540
+ display: flex;
541
+ flex-direction: column;
542
+ align-items: center;
543
+ gap: 6px;
544
+ }
545
+
546
+ .placed-widget .metric-label {
547
+ font-size: calc(11px * var(--font-scale));
548
+ font-weight: 600;
549
+ color: var(--text-muted);
550
+ text-transform: uppercase;
551
+ letter-spacing: 0.5px;
552
+ }
553
+
554
+ .placed-widget .progress-ring-container {
555
+ position: relative;
556
+ width: 64px;
557
+ height: 64px;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ }
562
+
563
+ .placed-widget .progress-ring {
564
+ width: 100%;
565
+ height: 100%;
566
+ }
567
+
568
+ .placed-widget .progress-ring-text {
569
+ position: absolute;
570
+ top: 50%;
571
+ left: 50%;
572
+ transform: translate(-50%, -50%);
573
+ font-size: calc(12px * var(--font-scale));
574
+ font-weight: 700;
575
+ color: var(--text-primary);
576
+ text-align: center;
577
+ line-height: 1;
578
+ }
579
+
538
580
  /* Dash Cards */
539
581
  .placed-widget .dash-card {
540
582
  background: var(--bg-secondary);
@@ -1,4 +1,4 @@
1
- /* LobsterBoard v0.7.0 - Dashboard Styles */
1
+ /* LobsterBoard v0.8.1 - Dashboard Styles */
2
2
  /* LobsterBoard Dashboard - Generated Styles */
3
3
 
4
4
  :root {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.7.0
2
+ * LobsterBoard v0.8.1
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -477,6 +477,148 @@ const WIDGETS = {
477
477
  document.getElementById('${props.id}-refresh').textContent =
478
478
  new Date().toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
479
479
  `
480
+ },
481
+
482
+ 'system-graphical': {
483
+ name: 'System (Graphical)',
484
+ icon: '💻',
485
+ category: 'system',
486
+ description: 'Graphical CPU and Memory usage with circular progress rings. Supports remote servers via lobsterboard-agent.',
487
+ defaultWidth: 240,
488
+ defaultHeight: 140,
489
+ hasApiKey: false,
490
+ properties: {
491
+ title: 'System',
492
+ server: 'local',
493
+ refreshInterval: 5,
494
+ showPercentages: true,
495
+ showLabels: true
496
+ },
497
+ preview: `<div style="display:flex;align-items:center;justify-content:space-around;padding:8px;">
498
+ <div style="position:relative;width:50px;height:50px;">
499
+ <svg viewBox="0 0 48 48" style="width:100%;height:100%;transform:rotate(-90deg);">
500
+ <circle cx="24" cy="24" r="18" fill="none" stroke="#30363d" stroke-width="4"/>
501
+ <circle cx="24" cy="24" r="18" fill="none" stroke="#58a6ff" stroke-width="4"
502
+ stroke-dasharray="113" stroke-dashoffset="85" stroke-linecap="round"/>
503
+ </svg>
504
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:10px;font-weight:600;color:#58a6ff;">25%</div>
505
+ </div>
506
+ <div style="position:relative;width:50px;height:50px;">
507
+ <svg viewBox="0 0 48 48" style="width:100%;height:100%;transform:rotate(-90deg);">
508
+ <circle cx="24" cy="24" r="18" fill="none" stroke="#30363d" stroke-width="4"/>
509
+ <circle cx="24" cy="24" r="18" fill="none" stroke="#3fb950" stroke-width="4"
510
+ stroke-dasharray="113" stroke-dashoffset="68" stroke-linecap="round"/>
511
+ </svg>
512
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:10px;font-weight:600;color:#3fb950;">40%</div>
513
+ </div>
514
+ </div>`,
515
+ generateHtml: (props) => `
516
+ <div class="dash-card" id="widget-${props.id}" style="height:100%;">
517
+ <div class="dash-card-head">
518
+ <span class="dash-card-title">${renderIcon('cpu')} ${props.title || 'System'}</span>
519
+ ${props.server && props.server !== 'local' ? `<span class="dash-card-badge" style="font-size:10px;">🌐</span>` : ''}
520
+ </div>
521
+ <div class="dash-card-body" style="display:flex;align-items:center;justify-content:space-around;padding:12px;">
522
+ <div class="system-metric">
523
+ ${props.showLabels ? '<div class="metric-label">CPU</div>' : ''}
524
+ <div class="progress-ring-container">
525
+ <svg class="progress-ring" viewBox="0 0 48 48">
526
+ <circle cx="24" cy="24" r="18" fill="none" stroke="var(--bg-tertiary)" stroke-width="4"/>
527
+ <circle id="${props.id}-cpu-ring" cx="24" cy="24" r="18" fill="none" stroke="#58a6ff" stroke-width="4"
528
+ stroke-dasharray="113.1" stroke-dashoffset="113.1" stroke-linecap="round"
529
+ style="transition: stroke-dashoffset 0.6s ease, stroke 0.3s ease; transform: rotate(-90deg); transform-origin: 50% 50%;"/>
530
+ </svg>
531
+ <div class="progress-ring-text">
532
+ <span id="${props.id}-cpu-pct" ${props.showPercentages ? '' : 'style="display:none;"'}>—</span>
533
+ <span id="${props.id}-cpu-icon" ${props.showPercentages ? 'style="display:none;"' : ''}>💻</span>
534
+ </div>
535
+ </div>
536
+ </div>
537
+ <div class="system-metric">
538
+ ${props.showLabels ? '<div class="metric-label">MEM</div>' : ''}
539
+ <div class="progress-ring-container">
540
+ <svg class="progress-ring" viewBox="0 0 48 48">
541
+ <circle cx="24" cy="24" r="18" fill="none" stroke="var(--bg-tertiary)" stroke-width="4"/>
542
+ <circle id="${props.id}-mem-ring" cx="24" cy="24" r="18" fill="none" stroke="#3fb950" stroke-width="4"
543
+ stroke-dasharray="113.1" stroke-dashoffset="113.1" stroke-linecap="round"
544
+ style="transition: stroke-dashoffset 0.6s ease, stroke 0.3s ease; transform: rotate(-90deg); transform-origin: 50% 50%;"/>
545
+ </svg>
546
+ <div class="progress-ring-text">
547
+ <span id="${props.id}-mem-pct" ${props.showPercentages ? '' : 'style="display:none;"'}>—</span>
548
+ <span id="${props.id}-mem-icon" ${props.showPercentages ? 'style="display:none;"' : ''}>🧠</span>
549
+ </div>
550
+ </div>
551
+ </div>
552
+ </div>
553
+ </div>`,
554
+ generateJs: (props) => `
555
+ // System (Graphical) Widget: ${props.id} — ${props.server === 'local' ? 'local SSE' : 'remote: ' + props.server}
556
+
557
+ function getUsageColor(percentage) {
558
+ if (percentage >= 90) return '#f85149'; // Red for critical
559
+ if (percentage >= 75) return '#d29922'; // Yellow for warning
560
+ if (percentage >= 50) return '#58a6ff'; // Blue for moderate
561
+ return '#3fb950'; // Green for good
562
+ }
563
+
564
+ function updateProgressRing(ringId, percentage, textId) {
565
+ const ring = document.getElementById(ringId);
566
+ const text = document.getElementById(textId);
567
+ if (!ring || !text) return;
568
+
569
+ const normalizedPct = Math.max(0, Math.min(100, percentage || 0));
570
+ const circumference = 113.1; // 2 * π * 18
571
+ const offset = circumference - (normalizedPct / 100 * circumference);
572
+ const color = getUsageColor(normalizedPct);
573
+
574
+ ring.style.strokeDashoffset = offset;
575
+ ring.style.stroke = color;
576
+ text.textContent = Math.round(normalizedPct) + '%';
577
+ text.style.color = color;
578
+ }
579
+
580
+ onStats('${props.server || 'local'}', function(data) {
581
+ // Handle offline state
582
+ if (data._offline) {
583
+ document.getElementById('${props.id}-cpu-pct').textContent = '⚠️';
584
+ document.getElementById('${props.id}-mem-pct').textContent = '⚠️';
585
+ document.getElementById('${props.id}-cpu-ring').style.strokeDashoffset = '113.1';
586
+ document.getElementById('${props.id}-mem-ring').style.strokeDashoffset = '113.1';
587
+ return;
588
+ }
589
+
590
+ // Update CPU ring
591
+ if (data.cpu && data.cpu.currentLoad != null) {
592
+ updateProgressRing('${props.id}-cpu-ring', data.cpu.currentLoad, '${props.id}-cpu-pct');
593
+ }
594
+
595
+ // Update Memory ring
596
+ if (data.memory && data.memory.total && data.memory.active != null) {
597
+ const memoryPct = (data.memory.active / data.memory.total) * 100;
598
+ updateProgressRing('${props.id}-mem-ring', memoryPct, '${props.id}-mem-pct');
599
+ }
600
+ }, ${(props.refreshInterval || 5) * 1000});
601
+ `
602
+ },
603
+
604
+ 'claude-usage': {
605
+ name: 'Claude Usage',
606
+ icon: '🤖',
607
+ category: 'large',
608
+ description: 'Real-time Claude Code subscription usage (5h session, 7d weekly, Opus, Sonnet limits). Reads credentials from ~/.claude.',
609
+ defaultWidth: 380,
610
+ defaultHeight: 260,
611
+ hasApiKey: false,
612
+ properties: { title: 'Claude Usage', refreshInterval: 120 },
613
+ preview: `<div style="padding:8px;font-size:11px;"><div><b>5h Session</b> 28%</div><div><b>7d Weekly</b> 31%</div></div>`,
614
+ generateHtml: (props) => `<div class="dash-card" id="widget-${props.id}" style="height:100%;"><div class="dash-card-head"><span class="dash-card-title">🤖 ${props.title || 'Claude Usage'}</span><span id="${props.id}-sub" style="font-size:10px;color:#8b949e;margin-left:auto;"></span></div><div class="dash-card-body" id="${props.id}-body" style="padding:8px 12px;overflow-y:auto;"><div style="color:#8b949e;text-align:center;">Loading...</div></div></div>`,
615
+ generateJs: (props) => `
616
+ function barColor(pct) { return pct >= 80 ? '#f85149' : pct >= 50 ? '#d29922' : '#3fb950'; }
617
+ function timeLeft(iso) { if (!iso) return ''; const ms = new Date(iso) - Date.now(); if (ms <= 0) return 'now'; const h = Math.floor(ms/3600000), m = Math.floor((ms%3600000)/60000); return h > 0 ? h+'h '+m+'m' : m+'m'; }
618
+ function usageBar(label, pct, resetIso) { const p = Math.min(100,Math.max(0,pct||0)), c = barColor(p), reset = resetIso ? '<span style="color:#8b949e;font-size:10px;">resets '+timeLeft(resetIso)+'</span>' : ''; return '<div style="margin-bottom:10px;"><div style="display:flex;justify-content:space-between;margin-bottom:3px;"><span style="font-weight:600;font-size:12px;">'+label+'</span><span style="font-size:13px;font-weight:700;color:'+c+';">'+p.toFixed(0)+'%</span></div><div style="background:#21262d;border-radius:4px;height:8px;overflow:hidden;"><div style="width:'+p+'%;height:100%;background:'+c+';border-radius:4px;transition:width .5s;"></div></div>'+(reset?'<div style="text-align:right;margin-top:2px;">'+reset+'</div>':'')+'</div>'; }
619
+ async function update_${props.id.replace(/-/g,'_')}() { const body = document.getElementById('${props.id}-body'); const subEl = document.getElementById('${props.id}-sub'); try { const res = await fetch('/api/pages/claude-usage/usage'); const d = await res.json(); if (d.error) { body.innerHTML='<div style="color:#f85149;">'+d.error+'</div>'; return; } if (subEl) { subEl.textContent = {max:'Max (5×)',pro:'Pro',free:'Free'}[d.subscription]||d.subscription||''; } let html=''; if(d.five_hour) html+=usageBar('5h Session',d.five_hour.utilization,d.five_hour.resets_at); if(d.seven_day) html+=usageBar('7d Weekly',d.seven_day.utilization,d.seven_day.resets_at); if(d.seven_day_opus) html+=usageBar('Opus (7d)',d.seven_day_opus.utilization,d.seven_day_opus.resets_at); if(d.seven_day_sonnet&&d.seven_day_sonnet.utilization>0) html+=usageBar('Sonnet (7d)',d.seven_day_sonnet.utilization,d.seven_day_sonnet.resets_at); if(d.extra_usage&&d.extra_usage.is_enabled){const used=(d.extra_usage.used_credits/100).toFixed(2),limit=d.extra_usage.monthly_limit>0?(d.extra_usage.monthly_limit/100).toFixed(2):'∞';html+='<div style="margin-top:4px;padding-top:6px;border-top:1px solid #30363d;"><div style="display:flex;justify-content:space-between;font-size:11px;"><span style="color:#8b949e;">Extra Usage</span><span style="font-weight:600;">$'+used+' / $'+limit+'</span></div></div>';} if(!html) html='<div style="color:#8b949e;">No usage data</div>'; body.innerHTML=html; } catch(e) { console.error('Claude usage error:',e); body.innerHTML='<div style="color:#f85149;">Failed to load</div>'; } }
620
+ update_${props.id.replace(/-/g,'_')}(); setInterval(update_${props.id.replace(/-/g,'_')}, ${(props.refreshInterval||120)*1000});
621
+ `
480
622
  }
481
623
  };
482
624