claw-dashboard 1.8.4 → 2.0.0

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 (135) hide show
  1. package/.c8rc.json +38 -0
  2. package/.dockerignore +68 -0
  3. package/.github/dependabot.yml +45 -0
  4. package/.github/pull_request_template.md +39 -0
  5. package/.github/workflows/ci.yml +147 -0
  6. package/.github/workflows/release.yml +40 -0
  7. package/.github/workflows/security.yml +84 -0
  8. package/.husky/pre-commit +1 -0
  9. package/.planning/codebase/ARCHITECTURE.md +206 -0
  10. package/.planning/codebase/INTEGRATIONS.md +150 -0
  11. package/.planning/codebase/STACK.md +122 -0
  12. package/.planning/codebase/STRUCTURE.md +201 -0
  13. package/CHANGELOG.md +293 -0
  14. package/CONTRIBUTING.md +378 -0
  15. package/Dockerfile +54 -0
  16. package/FEATURES.md +195 -21
  17. package/README.md +83 -6
  18. package/TODO.md +28 -0
  19. package/build-cjs.js +127 -0
  20. package/cjs-shim.js +13 -0
  21. package/dist/clawdash +1729 -0
  22. package/dist/clawdash.meta.json +2236 -0
  23. package/dist/widgets.cjs +6511 -0
  24. package/docker-compose.yml +67 -0
  25. package/docs/API.md +1050 -0
  26. package/docs/PLUGINS.md +1504 -0
  27. package/esbuild.config.js +158 -0
  28. package/eslint.config.js +56 -0
  29. package/examples/plugins/README.md +122 -0
  30. package/examples/plugins/api-status/index.js +294 -0
  31. package/examples/plugins/api-status/plugin.json +19 -0
  32. package/examples/plugins/hello-world/index.js +104 -0
  33. package/examples/plugins/hello-world/plugin.json +15 -0
  34. package/examples/plugins/system-metrics-chart/index.js +339 -0
  35. package/examples/plugins/system-metrics-chart/plugin.json +18 -0
  36. package/examples/plugins/weather-widget/index.js +136 -0
  37. package/examples/plugins/weather-widget/plugin.json +19 -0
  38. package/index.cjs +23005 -0
  39. package/index.js +5331 -512
  40. package/jest.config.js +11 -0
  41. package/man/clawdash.1 +276 -0
  42. package/package.json +54 -5
  43. package/schemas/plugin-manifest.json +128 -0
  44. package/scripts/release.js +595 -0
  45. package/src/alerts.js +693 -0
  46. package/src/auto-save.js +584 -0
  47. package/src/cache.js +390 -0
  48. package/src/checksum.js +146 -0
  49. package/src/cli/args.js +110 -0
  50. package/src/cli/export-schedule.js +423 -0
  51. package/src/cli/export-snapshot.js +138 -0
  52. package/src/cli/help.js +69 -0
  53. package/src/cli/import-snapshot.js +230 -0
  54. package/src/cli/index.js +14 -0
  55. package/src/cli/list-templates.js +69 -0
  56. package/src/cli/validate-config.js +76 -0
  57. package/src/cli/validate-plugin.js +187 -0
  58. package/src/cli/version.js +15 -0
  59. package/src/config-validator.js +586 -0
  60. package/src/config-watcher.js +401 -0
  61. package/src/config.js +684 -0
  62. package/src/container-detector.js +499 -0
  63. package/src/database.js +734 -0
  64. package/src/differential-render.js +327 -0
  65. package/src/errors.js +169 -0
  66. package/src/export-scheduler.js +581 -0
  67. package/src/gateway-manager.js +685 -0
  68. package/src/hints.js +371 -0
  69. package/src/loading-states.js +509 -0
  70. package/src/logger.js +185 -0
  71. package/src/memory-pressure.js +467 -0
  72. package/src/performance-monitor.js +374 -0
  73. package/src/plugin-errors.js +586 -0
  74. package/src/plugin-manifest-validator.js +219 -0
  75. package/src/plugin-reload.js +481 -0
  76. package/src/plugin-scaffold.js +1941 -0
  77. package/src/plugin-validator.js +284 -0
  78. package/src/retry.js +218 -0
  79. package/src/security.js +860 -0
  80. package/src/snapshot.js +372 -0
  81. package/src/splash.js +115 -0
  82. package/src/theme-selector.js +411 -0
  83. package/src/themes.js +874 -0
  84. package/src/transitions.js +534 -0
  85. package/src/types.d.ts +174 -0
  86. package/src/validation.js +926 -0
  87. package/src/web-server.js +885 -0
  88. package/src/widgets/builtin-widgets.js +1057 -0
  89. package/src/widgets/config-processor.js +401 -0
  90. package/src/widgets/dependency-resolver.js +596 -0
  91. package/src/widgets/index.js +79 -0
  92. package/src/widgets/plugin-api.js +845 -0
  93. package/src/widgets/widget-error-boundary.js +773 -0
  94. package/src/widgets/widget-error-isolation.js +551 -0
  95. package/src/widgets/widget-loader.js +1437 -0
  96. package/src/workers/system-worker.js +130 -0
  97. package/src/workers/worker-pool.js +633 -0
  98. package/tests/alerts.test.js +437 -0
  99. package/tests/auto-save.test.js +529 -0
  100. package/tests/cache.test.js +317 -0
  101. package/tests/cli.test.js +351 -0
  102. package/tests/command-palette.test.js +320 -0
  103. package/tests/config-processor.test.js +452 -0
  104. package/tests/config-validator.test.js +710 -0
  105. package/tests/config-watcher.test.js +594 -0
  106. package/tests/config.test.js +755 -0
  107. package/tests/database.test.js +438 -0
  108. package/tests/errors.test.js +189 -0
  109. package/tests/example-plugins.test.js +624 -0
  110. package/tests/integration.test.js +1321 -0
  111. package/tests/loading-states.test.js +300 -0
  112. package/tests/manifest-validation-on-load.test.js +671 -0
  113. package/tests/performance-monitor.test.js +190 -0
  114. package/tests/plugin-api-rate-limit.test.js +302 -0
  115. package/tests/plugin-errors.test.js +311 -0
  116. package/tests/plugin-lifecycle-e2e.test.js +1036 -0
  117. package/tests/plugin-reload.test.js +764 -0
  118. package/tests/rate-limiter.test.js +539 -0
  119. package/tests/retry.test.js +308 -0
  120. package/tests/security.test.js +411 -0
  121. package/tests/settings-modal.test.js +226 -0
  122. package/tests/theme-selector.test.js +57 -0
  123. package/tests/utils.js +242 -0
  124. package/tests/utils.test.js +317 -0
  125. package/tests/validate-plugin-cli.test.js +359 -0
  126. package/tests/validation.test.js +837 -0
  127. package/tests/web-server.test.js +646 -0
  128. package/tests/widget-arrange-mode.test.js +183 -0
  129. package/tests/widget-config-hot-reload.test.js +465 -0
  130. package/tests/widget-dependency.test.js +591 -0
  131. package/tests/widget-error-boundary.test.js +749 -0
  132. package/tests/widget-error-isolation.test.js +469 -0
  133. package/tests/widget-integration.test.js +1147 -0
  134. package/tests/widget-refresh-intervals.test.js +284 -0
  135. package/tests/worker-pool.test.js +522 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Hello World Widget Plugin
3
+ * The simplest possible widget example for Claw Dashboard
4
+ *
5
+ * This example demonstrates:
6
+ * - Basic widget structure extending BaseWidget
7
+ * - Required lifecycle methods (init, create, getData, render, destroy)
8
+ * - Simple configuration handling
9
+ * - Basic blessed UI creation
10
+ */
11
+
12
+ import { BaseWidget } from '../../../src/widgets/plugin-api.js';
13
+
14
+ /**
15
+ * HelloWidget - A minimal widget that displays a greeting
16
+ */
17
+ export default class HelloWidget extends BaseWidget {
18
+ constructor(options = {}) {
19
+ super(options);
20
+ this.name = options.name || 'Hello World';
21
+ this.description = 'Simple greeting widget';
22
+ }
23
+
24
+ /**
25
+ * Initialize the widget
26
+ * Called once when the widget is first loaded
27
+ */
28
+ async init() {
29
+ this.log('info', 'Hello World widget initialized');
30
+ return true;
31
+ }
32
+
33
+ /**
34
+ * Create the widget UI
35
+ * @param {Object} screen - Blessed screen object
36
+ * @param {Object} theme - Theme colors
37
+ */
38
+ async create(screen, theme = {}) {
39
+ const C = theme.colors || {};
40
+ const blessed = await import('blessed');
41
+
42
+ // Create a simple box with a border
43
+ this.box = blessed.default.box({
44
+ parent: screen,
45
+ width: '50%',
46
+ height: 5,
47
+ border: { type: 'line' },
48
+ label: ' HELLO ',
49
+ style: { border: { fg: C.green || 'green' } },
50
+ align: 'center',
51
+ valign: 'middle',
52
+ });
53
+
54
+ this.loaded = true;
55
+ this.log('debug', 'Hello World widget UI created');
56
+
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Get data for the widget
62
+ * Returns the message to display
63
+ */
64
+ async getData() {
65
+ const message = this.config.message || 'Hello, World!';
66
+ const showTimestamp = this.config.showTimestamp !== false;
67
+
68
+ return {
69
+ message,
70
+ timestamp: showTimestamp ? new Date().toISOString() : null,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Render the widget with data
76
+ * @param {Object} data - Data from getData()
77
+ */
78
+ render(data) {
79
+ if (!this.box) return;
80
+
81
+ let content = data.message;
82
+ if (data.timestamp) {
83
+ content += `\n[${data.timestamp}]`;
84
+ }
85
+
86
+ this.box.setContent(content);
87
+ }
88
+
89
+ /**
90
+ * Destroy the widget
91
+ * Clean up resources
92
+ */
93
+ async destroy() {
94
+ if (this.box) {
95
+ this.box.destroy();
96
+ this.box = null;
97
+ }
98
+ this.loaded = false;
99
+ this.log('info', 'Hello World widget destroyed');
100
+ }
101
+ }
102
+
103
+ // Export named export for flexibility
104
+ export { HelloWidget };
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "example-hello-world",
3
+ "name": "Hello World",
4
+ "description": "Simplest possible widget example - displays a greeting message",
5
+ "version": "1.0.0",
6
+ "author": "Claw Dashboard Team",
7
+ "category": "example",
8
+ "type": "widget",
9
+ "lazyLoad": true,
10
+ "priority": 100,
11
+ "config": {
12
+ "message": "Hello, World!",
13
+ "showTimestamp": false
14
+ }
15
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * System Metrics Chart Widget Plugin
3
+ * Demonstrates data visualization capabilities using blessed-contrib line charts
4
+ *
5
+ * This example shows:
6
+ * - Creating line charts with blessed-contrib
7
+ * - Managing time-series data with configurable history
8
+ * - Multiple metric support (CPU, memory, network)
9
+ * - Dynamic data updates with smooth rendering
10
+ * - Theme integration for consistent styling
11
+ */
12
+
13
+ import { BaseWidget } from '../../../src/widgets/plugin-api.js';
14
+
15
+ /**
16
+ * SystemMetricsChartWidget - Displays real-time system metrics as line charts
17
+ *
18
+ * Uses blessed-contrib line charts to visualize:
19
+ * - CPU usage percentage over time
20
+ * - Memory usage percentage over time
21
+ * - Network I/O rates over time
22
+ */
23
+ export default class SystemMetricsChartWidget extends BaseWidget {
24
+ constructor(options = {}) {
25
+ super(options);
26
+ this.name = options.name || 'System Metrics Chart';
27
+ this.description = 'Real-time system metrics visualization with line charts';
28
+
29
+ // Chart configuration
30
+ this.metricType = this.config.metricType || 'cpu';
31
+ this.maxDataPoints = this.config.maxDataPoints || 30;
32
+ this.refreshInterval = this.config.refreshInterval || 2000;
33
+ this.showLegend = this.config.showLegend !== false;
34
+ this.chartHeight = this.config.chartHeight || 15;
35
+
36
+ // Data storage for time series
37
+ this.dataHistory = {
38
+ cpu: { labels: [], values: [] },
39
+ memory: { labels: [], values: [] },
40
+ network: { labels: [], values: [] },
41
+ };
42
+
43
+ // Widget state
44
+ this.chart = null;
45
+ this.refreshTimer = null;
46
+ this.initialized = false;
47
+ }
48
+
49
+ /**
50
+ * Initialize the widget
51
+ * Called once when the widget is first loaded
52
+ */
53
+ async init() {
54
+ this.log('info', `System Metrics Chart initialized (metric: ${this.metricType})`);
55
+ return true;
56
+ }
57
+
58
+ /**
59
+ * Create the widget UI with blessed-contrib line chart
60
+ * @param {Object} screen - Blessed screen object
61
+ * @param {Object} theme - Theme colors
62
+ */
63
+ async create(screen, theme = {}) {
64
+ const C = theme.colors || {};
65
+ const blessed = await import('blessed');
66
+ const contrib = await import('blessed-contrib');
67
+
68
+ this.screen = screen;
69
+ this.theme = theme;
70
+
71
+ // Create main container box
72
+ this.box = blessed.default.box({
73
+ parent: screen,
74
+ width: '70%',
75
+ height: this.chartHeight + 2,
76
+ border: { type: 'line' },
77
+ label: ` SYSTEM METRICS (${this.metricType.toUpperCase()}) `,
78
+ style: {
79
+ border: { fg: C.cyan || 'cyan' },
80
+ },
81
+ });
82
+
83
+ // Create the line chart using blessed-contrib
84
+ this.chart = contrib.default.line({
85
+ parent: this.box,
86
+ top: 1,
87
+ left: 1,
88
+ width: '95%',
89
+ height: this.chartHeight - 2,
90
+ style: {
91
+ line: C.green || 'green',
92
+ text: C.white || 'white',
93
+ baseline: C.gray || 'gray',
94
+ },
95
+ xLabelPadding: 3,
96
+ xPadding: 5,
97
+ numYLabels: 5,
98
+ showNthLabel: Math.ceil(this.maxDataPoints / 6),
99
+ showLegend: this.showLegend,
100
+ legend: {
101
+ width: 12,
102
+ },
103
+ minY: 0,
104
+ maxY: 100,
105
+ wholeNumbersOnly: true,
106
+ });
107
+
108
+ // Add metric info text
109
+ this.infoText = blessed.default.text({
110
+ parent: this.box,
111
+ bottom: 0,
112
+ left: 1,
113
+ content: 'Initializing...',
114
+ style: {
115
+ fg: C.gray || 'gray',
116
+ },
117
+ });
118
+
119
+ this.loaded = true;
120
+ this.initialized = true;
121
+ this.log('debug', 'System Metrics Chart UI created');
122
+
123
+ // Start auto-refresh
124
+ this.startAutoRefresh();
125
+
126
+ return this;
127
+ }
128
+
129
+ /**
130
+ * Generate sample metrics data
131
+ * In a real implementation, this would fetch from system APIs
132
+ */
133
+ async getData() {
134
+ const now = new Date();
135
+ const timeLabel = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
136
+
137
+ // Generate realistic-looking data based on metric type
138
+ let value;
139
+ let unit;
140
+
141
+ switch (this.metricType) {
142
+ case 'cpu': {
143
+ // Simulate CPU percentage with some randomness but realistic patterns
144
+ const baseLoad = 30;
145
+ const variance = Math.random() * 40;
146
+ const spike = Math.random() > 0.9 ? 20 : 0; // Occasional spike
147
+ value = Math.min(100, Math.max(0, Math.floor(baseLoad + variance + spike)));
148
+ unit = '%';
149
+ break;
150
+ }
151
+ case 'memory': {
152
+ // Simulate memory percentage (trending data)
153
+ const baseMem = 50;
154
+ const memVariance = Math.random() * 20;
155
+ value = Math.min(100, Math.max(0, Math.floor(baseMem + memVariance)));
156
+ unit = '%';
157
+ break;
158
+ }
159
+ case 'network': {
160
+ // Simulate network rate (0-50 MB/s)
161
+ value = Math.floor(Math.random() * 50);
162
+ unit = ' MB/s';
163
+ break;
164
+ }
165
+ default: {
166
+ value = Math.floor(Math.random() * 100);
167
+ unit = '';
168
+ }
169
+ }
170
+
171
+ // Store in history
172
+ const history = this.dataHistory[this.metricType];
173
+ history.labels.push(timeLabel);
174
+ history.values.push(value);
175
+
176
+ // Trim to max data points
177
+ if (history.labels.length > this.maxDataPoints) {
178
+ history.labels.shift();
179
+ history.values.shift();
180
+ }
181
+
182
+ return {
183
+ metricType: this.metricType,
184
+ currentValue: value,
185
+ unit,
186
+ timestamp: now.toISOString(),
187
+ labels: [...history.labels],
188
+ values: [...history.values],
189
+ dataPoints: history.values.length,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Render the chart with data
195
+ * @param {Object} data - Metrics data from getData()
196
+ */
197
+ render(data) {
198
+ if (!this.chart || !data) return;
199
+
200
+ // Prepare chart data format for blessed-contrib
201
+ const chartData = {
202
+ title: `${this.metricType.toUpperCase()}`,
203
+ x: data.labels,
204
+ y: data.values,
205
+ style: this.getMetricStyle(this.metricType),
206
+ };
207
+
208
+ // Update the chart
209
+ this.chart.setData([chartData]);
210
+
211
+ // Update info text
212
+ const avg = data.values.length > 0
213
+ ? Math.floor(data.values.reduce((a, b) => a + b, 0) / data.values.length)
214
+ : 0;
215
+ const current = data.currentValue;
216
+ const max = data.values.length > 0 ? Math.max(...data.values) : 0;
217
+
218
+ this.infoText.setContent(
219
+ `Current: ${current}${data.unit} | Average: ${avg}${data.unit} | Peak: ${max}${data.unit} | Points: ${data.dataPoints}/${this.maxDataPoints}`
220
+ );
221
+ }
222
+
223
+ /**
224
+ * Get style configuration for different metric types
225
+ * @param {string} metricType - Type of metric
226
+ * @returns {Object} Style configuration
227
+ */
228
+ getMetricStyle(metricType) {
229
+ const C = this.theme?.colors || {};
230
+
231
+ const styles = {
232
+ cpu: {
233
+ line: C.green || 'green',
234
+ text: C.brightGreen || 'bright-green',
235
+ },
236
+ memory: {
237
+ line: C.yellow || 'yellow',
238
+ text: C.brightYellow || 'bright-yellow',
239
+ },
240
+ network: {
241
+ line: C.cyan || 'cyan',
242
+ text: C.brightCyan || 'bright-cyan',
243
+ },
244
+ };
245
+
246
+ return styles[metricType] || styles.cpu;
247
+ }
248
+
249
+ /**
250
+ * Switch to display a different metric type
251
+ * @param {string} metricType - Metric type ('cpu', 'memory', 'network')
252
+ */
253
+ switchMetric(metricType) {
254
+ if (!['cpu', 'memory', 'network'].includes(metricType)) {
255
+ this.log('warn', `Invalid metric type: ${metricType}`);
256
+ return;
257
+ }
258
+
259
+ this.metricType = metricType;
260
+
261
+ // Update label
262
+ if (this.box) {
263
+ this.box.setLabel(` SYSTEM METRICS (${metricType.toUpperCase()}) `);
264
+ }
265
+
266
+ // Adjust chart scale for network (different range)
267
+ if (this.chart && metricType === 'network') {
268
+ this.chart.options.maxY = 50; // MB/s
269
+ } else if (this.chart) {
270
+ this.chart.options.maxY = 100; // percentage
271
+ }
272
+
273
+ this.log('info', `Switched to ${metricType} metric display`);
274
+
275
+ // Trigger immediate refresh
276
+ this.getData().then(data => this.render(data));
277
+ }
278
+
279
+ /**
280
+ * Start auto-refresh timer
281
+ */
282
+ startAutoRefresh() {
283
+ this.stopAutoRefresh();
284
+
285
+ if (this.refreshInterval > 0) {
286
+ this.refreshTimer = setInterval(async () => {
287
+ try {
288
+ const data = await this.getData();
289
+ this.render(data);
290
+ } catch (err) {
291
+ this.log('error', `Auto-refresh failed: ${err.message}`);
292
+ }
293
+ }, this.refreshInterval);
294
+
295
+ this.log('debug', `Auto-refresh started (${this.refreshInterval}ms)`);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Stop auto-refresh timer
301
+ */
302
+ stopAutoRefresh() {
303
+ if (this.refreshTimer) {
304
+ clearInterval(this.refreshTimer);
305
+ this.refreshTimer = null;
306
+ this.log('debug', 'Auto-refresh stopped');
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Destroy the widget and clean up resources
312
+ */
313
+ async destroy() {
314
+ this.stopAutoRefresh();
315
+
316
+ // Clear data history
317
+ this.dataHistory = {
318
+ cpu: { labels: [], values: [] },
319
+ memory: { labels: [], values: [] },
320
+ network: { labels: [], values: [] },
321
+ };
322
+
323
+ if (this.chart) {
324
+ this.chart = null;
325
+ }
326
+
327
+ if (this.box) {
328
+ this.box.destroy();
329
+ this.box = null;
330
+ }
331
+
332
+ this.loaded = false;
333
+ this.initialized = false;
334
+ this.log('info', 'System Metrics Chart widget destroyed');
335
+ }
336
+ }
337
+
338
+ // Export named export for flexibility
339
+ export { SystemMetricsChartWidget };
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "example-system-metrics-chart",
3
+ "name": "System Metrics Chart",
4
+ "description": "Example widget demonstrating data visualization with blessed-contrib line charts for CPU, memory, and network metrics",
5
+ "version": "1.0.0",
6
+ "author": "Claw Dashboard Team",
7
+ "category": "example",
8
+ "type": "widget",
9
+ "lazyLoad": true,
10
+ "priority": 100,
11
+ "config": {
12
+ "metricType": "cpu",
13
+ "maxDataPoints": 30,
14
+ "refreshInterval": 2000,
15
+ "showLegend": true,
16
+ "chartHeight": 15
17
+ }
18
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Example Weather Widget Plugin
3
+ * Demonstrates how to create a custom widget for Claw Dashboard
4
+ */
5
+
6
+ import { BaseWidget } from '../../../src/widgets/plugin-api.js';
7
+
8
+ /**
9
+ * Weather Widget - Displays simulated weather data
10
+ * In a real implementation, this would fetch from a weather API
11
+ */
12
+ export default class WeatherWidget extends BaseWidget {
13
+ constructor(options = {}) {
14
+ super(options);
15
+ this.name = options.name || 'Weather';
16
+ this.description = 'Current weather conditions';
17
+ this.weatherData = null;
18
+ }
19
+
20
+ /**
21
+ * Initialize the widget
22
+ */
23
+ async init() {
24
+ this.log('info', 'Weather widget initialized');
25
+ return true;
26
+ }
27
+
28
+ /**
29
+ * Create the widget UI
30
+ * @param {Object} screen - Blessed screen object
31
+ * @param {Object} theme - Theme colors
32
+ */
33
+ async create(screen, theme = {}) {
34
+ const C = theme.colors || {};
35
+ const blessed = await import('blessed');
36
+
37
+ this.box = blessed.default.box({
38
+ parent: screen,
39
+ height: 5,
40
+ border: { type: 'line' },
41
+ label: ' WEATHER ',
42
+ style: { border: { fg: C.brightCyan || 'bright-cyan' } },
43
+ });
44
+
45
+ this.locationText = blessed.default.text({
46
+ parent: this.box,
47
+ top: 0,
48
+ left: 'center',
49
+ content: this.config.location || 'Unknown',
50
+ style: { fg: C.brightCyan || 'bright-cyan', bold: true },
51
+ });
52
+
53
+ this.tempText = blessed.default.text({
54
+ parent: this.box,
55
+ top: 1,
56
+ left: 'center',
57
+ content: '--°C',
58
+ style: { fg: C.white || 'white' },
59
+ });
60
+
61
+ this.conditionText = blessed.default.text({
62
+ parent: this.box,
63
+ top: 2,
64
+ left: 'center',
65
+ content: 'Loading...',
66
+ style: { fg: C.gray || 'gray' },
67
+ });
68
+
69
+ this.loaded = true;
70
+ this.log('debug', 'Weather widget UI created');
71
+
72
+ return this;
73
+ }
74
+
75
+ /**
76
+ * Get weather data
77
+ * In a real implementation, this would call a weather API
78
+ */
79
+ async getData() {
80
+ // Simulate API call with random weather data
81
+ const conditions = ['Sunny', 'Cloudy', 'Rainy', 'Snowy', 'Windy'];
82
+ const randomCondition = conditions[Math.floor(Math.random() * conditions.length)];
83
+ const randomTemp = Math.floor(Math.random() * 30) - 5; // -5 to 25°C
84
+
85
+ // Simulate network delay
86
+ await new Promise(resolve => setTimeout(resolve, 100));
87
+
88
+ return {
89
+ location: this.config.location || 'Local',
90
+ temperature: randomTemp,
91
+ condition: randomCondition,
92
+ humidity: Math.floor(Math.random() * 40) + 40, // 40-80%
93
+ windSpeed: Math.floor(Math.random() * 20) + 5, // 5-25 km/h
94
+ timestamp: new Date().toISOString(),
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Update the display
100
+ * @param {Object} data - Weather data
101
+ */
102
+ update(data) {
103
+ if (!this.box || !data) return;
104
+
105
+ this.weatherData = data;
106
+
107
+ this.locationText.setContent(data.location);
108
+ this.tempText.setContent(`${data.temperature}°C`);
109
+ this.conditionText.setContent(
110
+ `${data.condition} • ${data.humidity}% humidity`
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Render the widget
116
+ * @param {Object} data - Weather data from getData()
117
+ */
118
+ render(data) {
119
+ this.update(data);
120
+ }
121
+
122
+ /**
123
+ * Destroy the widget
124
+ */
125
+ async destroy() {
126
+ if (this.box) {
127
+ this.box.destroy();
128
+ this.box = null;
129
+ }
130
+ this.loaded = false;
131
+ this.log('info', 'Weather widget destroyed');
132
+ }
133
+ }
134
+
135
+ // Also export as named export for flexibility
136
+ export { WeatherWidget };
@@ -0,0 +1,19 @@
1
+ {
2
+ "id": "example-weather",
3
+ "name": "Weather",
4
+ "description": "Example weather widget showing current conditions (demonstrates env var interpolation)",
5
+ "version": "1.0.0",
6
+ "author": "Claw Dashboard Team",
7
+ "category": "example",
8
+ "type": "widget",
9
+ "lazyLoad": true,
10
+ "priority": 100,
11
+ "config": {
12
+ "__version": "1.0.0",
13
+ "apiKey": "${WEATHER_API_KEY:-demo-key}",
14
+ "location": "${WEATHER_LOCATION:-New York}",
15
+ "unit": "${WEATHER_UNIT:-celsius}",
16
+ "refreshInterval": 300000,
17
+ "baseUrl": "${WEATHER_API_URL:-https://api.weather.example.com}"
18
+ }
19
+ }