claw-dashboard 2.0.0 → 2.1.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.
@@ -25,10 +25,10 @@ jobs:
25
25
 
26
26
  steps:
27
27
  - name: Checkout code
28
- uses: actions/checkout@v4
28
+ uses: actions/checkout@v6
29
29
 
30
30
  - name: Setup Node.js ${{ matrix.node-version }}
31
- uses: actions/setup-node@v4
31
+ uses: actions/setup-node@v6
32
32
  with:
33
33
  node-version: ${{ matrix.node-version }}
34
34
  cache: 'npm'
@@ -48,7 +48,7 @@ jobs:
48
48
 
49
49
  - name: Upload coverage to Codecov
50
50
  if: matrix.node-version == 20 && github.event_name != 'pull_request'
51
- uses: codecov/codecov-action@v4
51
+ uses: codecov/codecov-action@v5
52
52
  with:
53
53
  files: ./coverage/lcov.info
54
54
  fail_ci_if_error: false
@@ -63,10 +63,10 @@ jobs:
63
63
 
64
64
  steps:
65
65
  - name: Checkout code
66
- uses: actions/checkout@v4
66
+ uses: actions/checkout@v6
67
67
 
68
68
  - name: Setup Node.js
69
- uses: actions/setup-node@v4
69
+ uses: actions/setup-node@v6
70
70
  with:
71
71
  node-version: 20
72
72
  cache: 'npm'
@@ -78,7 +78,7 @@ jobs:
78
78
  run: npm run build
79
79
 
80
80
  - name: Upload build artifacts
81
- uses: actions/upload-artifact@v4
81
+ uses: actions/upload-artifact@v7
82
82
  with:
83
83
  name: dist
84
84
  path: dist/
@@ -92,7 +92,7 @@ jobs:
92
92
 
93
93
  steps:
94
94
  - name: Checkout code
95
- uses: actions/checkout@v4
95
+ uses: actions/checkout@v6
96
96
 
97
97
  - name: Set up Docker Buildx
98
98
  uses: docker/setup-buildx-action@v3
@@ -117,10 +117,10 @@ jobs:
117
117
 
118
118
  steps:
119
119
  - name: Checkout code
120
- uses: actions/checkout@v4
120
+ uses: actions/checkout@v6
121
121
 
122
122
  - name: Setup Node.js
123
- uses: actions/setup-node@v4
123
+ uses: actions/setup-node@v6
124
124
  with:
125
125
  node-version: 20
126
126
  cache: 'npm'
@@ -141,7 +141,7 @@ jobs:
141
141
  output: 'trivy-results.sarif'
142
142
 
143
143
  - name: Upload Trivy scan results
144
- uses: github/codeql-action/upload-sarif@v3
144
+ uses: github/codeql-action/upload-sarif@v4
145
145
  if: always()
146
146
  with:
147
147
  sarif_file: 'trivy-results.sarif'
@@ -14,10 +14,10 @@ jobs:
14
14
 
15
15
  steps:
16
16
  - name: Checkout code
17
- uses: actions/checkout@v4
17
+ uses: actions/checkout@v6
18
18
 
19
19
  - name: Setup Node.js
20
- uses: actions/setup-node@v4
20
+ uses: actions/setup-node@v6
21
21
  with:
22
22
  node-version: 20
23
23
  cache: 'npm'
@@ -17,10 +17,10 @@ jobs:
17
17
 
18
18
  steps:
19
19
  - name: Checkout code
20
- uses: actions/checkout@v4
20
+ uses: actions/checkout@v6
21
21
 
22
22
  - name: Setup Node.js
23
- uses: actions/setup-node@v4
23
+ uses: actions/setup-node@v6
24
24
  with:
25
25
  node-version: 20
26
26
  cache: 'npm'
@@ -45,7 +45,7 @@ jobs:
45
45
 
46
46
  steps:
47
47
  - name: Checkout code
48
- uses: actions/checkout@v4
48
+ uses: actions/checkout@v6
49
49
 
50
50
  - name: Dependency Review
51
51
  uses: actions/dependency-review-action@v4
@@ -68,17 +68,17 @@ jobs:
68
68
 
69
69
  steps:
70
70
  - name: Checkout code
71
- uses: actions/checkout@v4
71
+ uses: actions/checkout@v6
72
72
 
73
73
  - name: Initialize CodeQL
74
- uses: github/codeql-action/init@v3
74
+ uses: github/codeql-action/init@v4
75
75
  with:
76
76
  languages: ${{ matrix.language }}
77
77
 
78
78
  - name: Autobuild
79
- uses: github/codeql-action/autobuild@v3
79
+ uses: github/codeql-action/autobuild@v4
80
80
 
81
81
  - name: Perform CodeQL Analysis
82
- uses: github/codeql-action/analyze@v3
82
+ uses: github/codeql-action/analyze@v4
83
83
  with:
84
84
  category: "/language:${{matrix.language}}"
package/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import blessed from 'blessed';
4
- import contrib from 'blessed-contrib';
5
4
  import si from 'systeminformation';
6
5
  import { exec } from 'child_process';
7
6
  import { promisify } from 'util';
@@ -397,6 +396,22 @@ async function getLatestVersion() {
397
396
  } catch { return null; }
398
397
  }
399
398
 
399
+ async function getLatestDashboardVersion() {
400
+ try {
401
+ return await new Promise((resolve) => {
402
+ https.get('https://api.github.com/repos/spleck/claw-dashboard/releases/latest', {
403
+ headers: { 'User-Agent': 'claw-dashboard' }
404
+ }, (res) => {
405
+ let data = '';
406
+ res.on('data', chunk => data += chunk);
407
+ res.on('end', () => {
408
+ try { resolve(JSON.parse(data).tag_name?.replace(/^v/, '')); } catch { resolve(null); }
409
+ });
410
+ }).on('error', () => resolve(null)).setTimeout(3000);
411
+ });
412
+ } catch { return null; }
413
+ }
414
+
400
415
  function formatDuration(seconds) {
401
416
  if (!seconds || seconds < 0) return '--';
402
417
  const days = Math.floor(seconds / 86400);
@@ -836,7 +851,7 @@ class Dashboard {
836
851
  // Favorites state
837
852
  this.showFavoritesOnly = this.settings.showFavoritesOnly || false;
838
853
  this.history = { cpu: new Array(HISTORY_LENGTH).fill(0), memory: new Array(HISTORY_LENGTH).fill(0), netRx: new Array(NETWORK_HISTORY_LENGTH).fill(0), netTx: new Array(NETWORK_HISTORY_LENGTH).fill(0) };
839
- this.data = { cpu: [], memory: {}, openclaw: null, gpu: null, network: null, sessions: [], agents: [], version: null, latest: null, sessionTPS: {}, sessionLastTPS: {} };
854
+ this.data = { cpu: [], memory: {}, openclaw: null, gpu: null, network: null, sessions: [], agents: [], version: null, latest: null, dashboardVersion: DASHBOARD_VERSION, dashboardLatest: null, sessionTPS: {}, sessionLastTPS: {} };
840
855
  // Data freshness tracking - stores timestamps when each data type was last successfully fetched
841
856
  this.dataTimestamps = { cpu: null, memory: null, gpu: null, network: null, disk: null, system: null, sessions: null };
842
857
  this.prev = null;
@@ -1265,6 +1280,13 @@ class Dashboard {
1265
1280
  }).catch(() => {
1266
1281
  this.data.latest = null;
1267
1282
  });
1283
+
1284
+ // Also check for dashboard updates
1285
+ getLatestDashboardVersion().then(latest => {
1286
+ this.data.dashboardLatest = latest;
1287
+ }).catch(() => {
1288
+ this.data.dashboardLatest = null;
1289
+ });
1268
1290
  }
1269
1291
 
1270
1292
  createWidgets() {
@@ -5081,11 +5103,23 @@ class Dashboard {
5081
5103
  }
5082
5104
  }
5083
5105
 
5106
+ // Fetch gateway uptime FIRST - if process isn't running, gateway is down
5107
+ // This must be checked before fetchSessions() since it can succeed via local fallback
5108
+ this.data.gatewayUptime = await getGatewayUptime();
5109
+ const isGatewayProcessRunning = this.data.gatewayUptime !== null;
5110
+
5084
5111
  // Fetch sessions via API (same as clawps) - has displayName and channel
5085
5112
  try {
5086
5113
  const sessions = await timeOperation('Sessions', () => this.fetchSessions(), 5000);
5087
5114
  this.data.sessions = sessions || [];
5088
- this.data.openclaw = { gateway: { reachable: true } };
5115
+ // Gateway is ONLY reachable if: process is running AND at least one endpoint responds
5116
+ const stats = this.data.gatewayStats;
5117
+ const allUnreachable = stats && stats.totalEndpoints > 0 && stats.reachableEndpoints === 0;
5118
+ if (!isGatewayProcessRunning || allUnreachable) {
5119
+ this.data.openclaw = { gateway: { reachable: false } };
5120
+ } else {
5121
+ this.data.openclaw = { gateway: { reachable: true } };
5122
+ }
5089
5123
  this.dataTimestamps.sessions = now;
5090
5124
 
5091
5125
  // Clean up stale sessionTPS entries for sessions that no longer exist
@@ -5437,7 +5471,18 @@ class Dashboard {
5437
5471
  openclawText = `openclaw ${current}`;
5438
5472
  }
5439
5473
  }
5440
- this.diffRenderer.setContent('title', this.w.title, `Dashboard ${DASHBOARD_VERSION}, ${openclawText}`);
5474
+
5475
+ // Render dashboard version with update check
5476
+ let dashboardText = `dashboard ${this.data.dashboardVersion}`;
5477
+ if (this.data.dashboardLatest && this.data.dashboardVersion) {
5478
+ if (this.data.dashboardVersion === this.data.dashboardLatest) {
5479
+ dashboardText = `dashboard ${this.data.dashboardVersion} ✓`;
5480
+ } else {
5481
+ dashboardText = `dashboard ${this.data.dashboardVersion} → ${this.data.dashboardLatest}`;
5482
+ }
5483
+ }
5484
+
5485
+ this.diffRenderer.setContent('title', this.w.title, `Claw ${dashboardText}, ${openclawText}`);
5441
5486
 
5442
5487
  // Update clock - show current local time, PAUSED indicator on the right (with differential updates)
5443
5488
  const now = new Date();
@@ -5588,9 +5633,12 @@ class Dashboard {
5588
5633
  const gatewayHealth = gatewayManager.getEndpointHealth();
5589
5634
  const unreachableCount = gatewayHealth.filter(ep => ep.enabled && !ep.reachable).length;
5590
5635
  const enabledCount = gatewayHealth.filter(ep => ep.enabled).length;
5636
+ const isGatewayProcessRunning = this.data.gatewayUptime !== null;
5591
5637
  let gatewayIndicator = '';
5592
5638
  if (enabledCount > 0) {
5593
- if (unreachableCount === 0) {
5639
+ if (!isGatewayProcessRunning) {
5640
+ gatewayIndicator = '{red-fg}✗ gateway offline{/red-fg} [G] retry ';
5641
+ } else if (unreachableCount === 0) {
5594
5642
  gatewayIndicator = '{green-fg}● gateway{/green-fg} ';
5595
5643
  } else if (unreachableCount === enabledCount) {
5596
5644
  gatewayIndicator = '{red-fg}✗ gateway offline{/red-fg} [G] retry ';
@@ -5925,5 +5973,6 @@ async function main() {
5925
5973
  // Run main
5926
5974
  main().catch(err => {
5927
5975
  console.error('Fatal error:', err);
5976
+ if (err.stack) console.error('Stack:', err.stack);
5928
5977
  process.exit(1);
5929
5978
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-dashboard",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "A beautiful console dashboard for monitoring OpenClaw instances — inspired by htop/btop/mactop",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -60,8 +60,8 @@
60
60
  "node": ">=18"
61
61
  },
62
62
  "dependencies": {
63
- "blessed": "^0.1.81",
64
- "blessed-contrib": "^1.0.11",
63
+ "@pm2/blessed": "^0.1.81",
64
+ "blessed": "npm:@pm2/blessed@^0.1.81",
65
65
  "chalk": "^5.3.0",
66
66
  "sql.js": "^1.14.0",
67
67
  "systeminformation": "^5.21.22"
@@ -69,7 +69,7 @@
69
69
  "devDependencies": {
70
70
  "@eslint/js": "^10.0.1",
71
71
  "c8": "^11.0.0",
72
- "esbuild": "^0.25.12",
72
+ "esbuild": "^0.27.3",
73
73
  "eslint": "^10.0.2",
74
74
  "husky": "^9.1.7",
75
75
  "jest": "^30.2.0",
@@ -585,7 +585,7 @@ export { ${className} };
585
585
 
586
586
  chart: {
587
587
  name: 'Chart Widget',
588
- description: 'Widget with real-time line chart visualization using blessed-contrib',
588
+ description: 'Widget with real-time line chart visualization using ASCII art',
589
589
  manifest: (id, name, author, options = {}) => ({
590
590
  id,
591
591
  name: name || id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
@@ -607,7 +607,7 @@ export { ${className} };
607
607
 
608
608
  widgetCode: (id, className) => `/**
609
609
  * ${className} Widget Plugin
610
- * Chart widget with real-time data visualization using blessed-contrib
610
+ * Chart widget with real-time data visualization using ASCII art
611
611
  */
612
612
 
613
613
  import { BaseWidget } from 'claw-dashboard/widgets';
@@ -644,14 +644,13 @@ export default class ${className} extends BaseWidget {
644
644
  }
645
645
 
646
646
  /**
647
- * Create the widget UI with blessed-contrib line chart
647
+ * Create the widget UI with ASCII line chart
648
648
  * @param {Object} screen - Blessed screen object
649
649
  * @param {Object} theme - Theme colors
650
650
  */
651
651
  async create(screen, theme = {}) {
652
652
  const C = theme.colors || {};
653
653
  const blessed = await import('blessed');
654
- const contrib = await import('blessed-contrib');
655
654
 
656
655
  this.screen = screen;
657
656
  this.theme = theme;
@@ -668,27 +667,15 @@ export default class ${className} extends BaseWidget {
668
667
  },
669
668
  });
670
669
 
671
- // Create the line chart using blessed-contrib
672
- this.chart = contrib.default.line({
670
+ // Create ASCII chart area using text element
671
+ this.chartArea = blessed.default.text({
673
672
  parent: this.box,
674
673
  top: 1,
675
674
  left: 1,
676
675
  width: '95%',
677
- height: 14,
678
- style: {
679
- line: C.green || 'green',
680
- text: C.white || 'white',
681
- baseline: C.gray || 'gray',
682
- },
683
- xLabelPadding: 3,
684
- xPadding: 5,
685
- numYLabels: 5,
686
- showNthLabel: Math.ceil(this.maxDataPoints / 6),
687
- showLegend: this.showLegend,
688
- legend: { width: 12 },
689
- minY: 0,
690
- maxY: 100,
691
- wholeNumbersOnly: true,
676
+ height: 13,
677
+ tags: true,
678
+ style: { fg: C.green || 'green' },
692
679
  });
693
680
 
694
681
  // Add info text
@@ -709,6 +696,29 @@ export default class ${className} extends BaseWidget {
709
696
  return this;
710
697
  }
711
698
 
699
+ /**
700
+ * Generate ASCII chart from data
701
+ */
702
+ _renderAsciiChart(values, width = 60, height = 10) {
703
+ if (!values || values.length === 0) return 'No data';
704
+
705
+ const min = Math.min(...values);
706
+ const max = Math.max(...values);
707
+ const range = max - min || 1;
708
+
709
+ let chart = '';
710
+ for (let row = height - 1; row >= 0; row--) {
711
+ const threshold = min + (range * row / height);
712
+ let line = '';
713
+ for (let i = 0; i < Math.min(values.length, width); i++) {
714
+ const val = values[i];
715
+ line += val >= threshold ? '█' : '░';
716
+ }
717
+ chart += line + '\n';
718
+ }
719
+ return chart;
720
+ }
721
+
712
722
  /**
713
723
  * Generate sample data - customize this for your data source
714
724
  */
@@ -748,16 +758,9 @@ export default class ${className} extends BaseWidget {
748
758
  render(data) {
749
759
  if (!this.chart || !data) return;
750
760
 
751
- // Prepare chart data
752
- const chartData = {
753
- title: '${className}',
754
- x: data.labels,
755
- y: data.values,
756
- style: { line: 'green' },
757
- };
758
-
759
- // Update the chart
760
- this.chart.setData([chartData]);
761
+ // Generate ASCII chart
762
+ const asciiChart = this._renderAsciiChart(data.values);
763
+ this.chartArea.setContent(asciiChart);
761
764
 
762
765
  // Update info text
763
766
  const avg = data.values.length > 0
@@ -1102,7 +1105,6 @@ export default class ${className} extends BaseWidget {
1102
1105
  async create(screen, theme = {}) {
1103
1106
  const C = theme.colors || {};
1104
1107
  const blessed = await import('blessed');
1105
- const contrib = await import('blessed-contrib');
1106
1108
 
1107
1109
  this.screen = screen;
1108
1110
  this.theme = theme;
@@ -1119,35 +1121,16 @@ export default class ${className} extends BaseWidget {
1119
1121
  },
1120
1122
  });
1121
1123
 
1122
- // Create gauge based on type
1123
- if (this.gaugeType === 'circle') {
1124
- this.gauge = contrib.default.gauge({
1125
- parent: this.box,
1126
- top: 1,
1127
- left: 'center',
1128
- width: '90%',
1129
- height: 6,
1130
- style: {
1131
- label: { fg: C.white || 'white' },
1132
- value: { fg: C.green || 'green' },
1133
- },
1134
- label: this.name,
1135
- });
1136
- } else {
1137
- // Linear gauge
1138
- this.gauge = contrib.default.gauge({
1139
- parent: this.box,
1140
- top: 2,
1141
- left: 1,
1142
- width: '96%',
1143
- height: 4,
1144
- style: {
1145
- label: { fg: C.white || 'white' },
1146
- value: { fg: C.green || 'green' },
1147
- },
1148
- label: this.name,
1149
- });
1150
- }
1124
+ // Create ASCII gauge area
1125
+ this.gaugeArea = blessed.default.text({
1126
+ parent: this.box,
1127
+ top: 1,
1128
+ left: 'center',
1129
+ width: '90%',
1130
+ height: 6,
1131
+ tags: true,
1132
+ style: { fg: C.green || 'green' },
1133
+ });
1151
1134
 
1152
1135
  // Value display
1153
1136
  this.valueText = blessed.default.text({
@@ -1167,6 +1150,15 @@ export default class ${className} extends BaseWidget {
1167
1150
  return this;
1168
1151
  }
1169
1152
 
1153
+ /**
1154
+ * Generate ASCII gauge
1155
+ */
1156
+ _renderAsciiGauge(percent, width = 20) {
1157
+ const filled = Math.round((percent / 100) * width);
1158
+ const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
1159
+ return bar;
1160
+ }
1161
+
1170
1162
  /**
1171
1163
  * Generate sample data - customize this for your data source
1172
1164
  */
@@ -1186,14 +1178,11 @@ export default class ${className} extends BaseWidget {
1186
1178
  * Render the gauge with data
1187
1179
  */
1188
1180
  render(result) {
1189
- if (!this.gauge || !result) return;
1190
-
1191
- // Set gauge percentage (0-100)
1192
- this.gauge.setData(result.percentage);
1193
-
1194
- // Update value text
1195
- this.valueText.setContent(result.value + this.unit);
1181
+ if (!this.gaugeArea || !result) return;
1196
1182
 
1183
+ // Generate ASCII gauge
1184
+ const asciiGauge = this._renderAsciiGauge(result.percentage);
1185
+
1197
1186
  // Color based on value
1198
1187
  let color = 'green';
1199
1188
  if (result.percentage > 80) {
@@ -1202,6 +1191,11 @@ export default class ${className} extends BaseWidget {
1202
1191
  color = 'yellow';
1203
1192
  }
1204
1193
 
1194
+ // Update gauge display
1195
+ this.gaugeArea.setContent('{' + color + '-fg}' + asciiGauge + '{/' + color + '-fg}');
1196
+
1197
+ // Update value text
1198
+ this.valueText.setContent(result.value + this.unit);
1205
1199
  this.valueText.style.fg = this.theme?.colors?.[color] || color;
1206
1200
  }
1207
1201
 
@@ -1242,9 +1236,8 @@ export default class ${className} extends BaseWidget {
1242
1236
  async destroy() {
1243
1237
  this.stopAutoRefresh();
1244
1238
 
1245
- if (this.gauge) {
1246
- this.gauge.destroy();
1247
- this.gauge = null;
1239
+ if (this.gaugeArea) {
1240
+ this.gaugeArea = null;
1248
1241
  }
1249
1242
 
1250
1243
  if (this.box) {
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import blessed from 'blessed';
7
- import contrib from 'blessed-contrib';
8
7
  import { BaseWidget } from './plugin-api.js';
9
8
 
10
9
  /**
@@ -239,98 +238,6 @@ export class GpuWidget extends BaseWidget {
239
238
  }
240
239
  }
241
240
 
242
- /**
243
- * Network Widget - Displays network activity
244
- */
245
- export class NetworkWidget extends BaseWidget {
246
- constructor(options = {}) {
247
- super(options);
248
- this.name = 'Network';
249
- this.description = 'Network activity';
250
- this.history = [];
251
- this.maxHistory = 30;
252
- }
253
-
254
- async create(screen, theme = {}) {
255
- const C = theme.colors || {};
256
-
257
- this.box = blessed.box({
258
- parent: screen,
259
- height: 5,
260
- border: { type: 'line' },
261
- label: ' NETWORK ',
262
- style: { border: { fg: C.brightCyan || 'bright-cyan' } },
263
- });
264
-
265
- this.valueText = blessed.text({
266
- parent: this.box,
267
- top: 0,
268
- left: 'center',
269
- content: 'Loading...',
270
- style: { fg: C.brightCyan || 'bright-cyan', bold: true },
271
- });
272
-
273
- this.detailText = blessed.text({
274
- parent: this.box,
275
- top: 1,
276
- left: 'center',
277
- content: '',
278
- style: { fg: C.gray || 'gray' },
279
- });
280
-
281
- this.sparkline = contrib.sparkline({
282
- parent: this.box,
283
- top: 2,
284
- left: 'center',
285
- width: 20,
286
- height: 1,
287
- style: { fg: C.cyan || 'cyan' },
288
- });
289
-
290
- return this;
291
- }
292
-
293
- async getData(dataProvider) {
294
- if (dataProvider) {
295
- return dataProvider('network');
296
- }
297
- return null;
298
- }
299
-
300
- formatBytes(bytes) {
301
- if (bytes === 0) return '0 B';
302
- const k = 1024;
303
- const sizes = ['B', 'KB', 'MB', 'GB'];
304
- const i = Math.floor(Math.log(bytes) / Math.log(k));
305
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
306
- }
307
-
308
- update(data) {
309
- if (!data || !this.box) return;
310
-
311
- const iface = data.interface || 'unknown';
312
- const rx = data.rxSec || 0;
313
- const tx = data.txSec || 0;
314
- const total = rx + tx;
315
-
316
- // Update history
317
- this.history.push(total);
318
- if (this.history.length > this.maxHistory) {
319
- this.history.shift();
320
- }
321
-
322
- this.valueText.setContent(iface);
323
- this.detailText.setContent(`↓${this.formatBytes(rx)} ↑${this.formatBytes(tx)}`);
324
-
325
- if (this.sparkline && this.history.length > 1) {
326
- this.sparkline.setData([this.history]);
327
- }
328
- }
329
-
330
- render(data) {
331
- this.update(data);
332
- }
333
- }
334
241
 
335
242
  /**
336
243
  * Disk Widget - Displays disk usage
@@ -1010,7 +917,6 @@ export const WIDGET_REGISTRY = {
1010
917
  cpu: CpuWidget,
1011
918
  memory: MemoryWidget,
1012
919
  gpu: GpuWidget,
1013
- network: NetworkWidget,
1014
920
  disk: DiskWidget,
1015
921
  system: SystemWidget,
1016
922
  uptime: UptimeWidget,
@@ -1044,7 +950,6 @@ export default {
1044
950
  CpuWidget,
1045
951
  MemoryWidget,
1046
952
  GpuWidget,
1047
- NetworkWidget,
1048
953
  DiskWidget,
1049
954
  SystemWidget,
1050
955
  UptimeWidget,
@@ -45,7 +45,6 @@ export {
45
45
  CpuWidget,
46
46
  MemoryWidget,
47
47
  GpuWidget,
48
- NetworkWidget,
49
48
  DiskWidget,
50
49
  SystemWidget,
51
50
  UptimeWidget,
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Tests for removed dependencies and widgets
3
+ * Verifies cleanup after blessed-contrib and NetworkWidget removal
4
+ */
5
+
6
+ import { jest } from '@jest/globals';
7
+ import { describe, test, expect } from '@jest/globals';
8
+ import { readFileSync } from 'fs';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ describe('Removed dependencies cleanup', () => {
16
+ describe('blessed-contrib removal', () => {
17
+ test('should not have blessed-contrib in package.json dependencies', () => {
18
+ const pkg = JSON.parse(
19
+ readFileSync(join(__dirname, '../package.json'), 'utf8')
20
+ );
21
+ expect(pkg.dependencies).not.toHaveProperty('blessed-contrib');
22
+ });
23
+
24
+ test('should not import blessed-contrib in index.js', () => {
25
+ const indexContent = readFileSync(join(__dirname, '../index.js'), 'utf8');
26
+ expect(indexContent).not.toContain("import contrib from 'blessed-contrib'");
27
+ expect(indexContent).not.toContain('blessed-contrib');
28
+ });
29
+ });
30
+
31
+ describe('NetworkWidget removal', () => {
32
+ test('should not export NetworkWidget from widgets/index.js', async () => {
33
+ const widgets = await import('../src/widgets/index.js');
34
+ expect(widgets.NetworkWidget).toBeUndefined();
35
+ });
36
+
37
+ test('should not have NetworkWidget in builtin-widgets exports', async () => {
38
+ const builtin = await import('../src/widgets/builtin-widgets.js');
39
+ expect(builtin.NetworkWidget).toBeUndefined();
40
+ expect(builtin.WIDGET_REGISTRY).not.toHaveProperty('network');
41
+ });
42
+
43
+ test('builtin-widgets should not reference blessed-contrib', () => {
44
+ const content = readFileSync(
45
+ join(__dirname, '../src/widgets/builtin-widgets.js'),
46
+ 'utf8'
47
+ );
48
+ expect(content).not.toContain('blessed-contrib');
49
+ expect(content).not.toContain('contrib.sparkline');
50
+ });
51
+ });
52
+
53
+ describe('@pm2/blessed migration', () => {
54
+ test('should have @pm2/blessed as dependency', () => {
55
+ const pkg = JSON.parse(
56
+ readFileSync(join(__dirname, '../package.json'), 'utf8')
57
+ );
58
+ expect(pkg.dependencies).toHaveProperty('@pm2/blessed');
59
+ });
60
+
61
+ test('should have blessed aliased to @pm2/blessed', () => {
62
+ const pkg = JSON.parse(
63
+ readFileSync(join(__dirname, '../package.json'), 'utf8')
64
+ );
65
+ expect(pkg.dependencies.blessed).toBe('npm:@pm2/blessed@^0.1.81');
66
+ });
67
+
68
+ test('should be able to import blessed', async () => {
69
+ // This verifies the alias is working
70
+ const blessed = await import('blessed');
71
+ expect(typeof blessed.default).toBe('function');
72
+ });
73
+ });
74
+ });