claw-dashboard 1.9.0 → 2.1.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 +5285 -566
  40. package/jest.config.js +11 -0
  41. package/man/clawdash.1 +276 -0
  42. package/package.json +54 -6
  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 +1934 -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 +1056 -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
package/src/hints.js ADDED
@@ -0,0 +1,371 @@
1
+ import blessed from 'blessed';
2
+
3
+ import config from './config.js';
4
+ import logger from './logger.js';
5
+
6
+ const { PATHS, DASHBOARD_VERSION } = config;
7
+
8
+ // Hint definitions with contextual information for first-time users
9
+ const HINTS = [
10
+ {
11
+ id: 'navigation',
12
+ title: '📋 Navigation Tips',
13
+ content: [
14
+ 'Use ↑/↓ arrows to navigate sessions',
15
+ 'Use h/l or ←/→ to change pages',
16
+ 'Press Enter to select a session',
17
+ 'Press / to search sessions',
18
+ ],
19
+ position: { top: 'center', left: 'center' },
20
+ },
21
+ {
22
+ id: 'vi-mode',
23
+ title: '⌨️ Vi-Mode Navigation',
24
+ content: [
25
+ 'h / l : Previous/next page',
26
+ 'j / k : Select next/previous session',
27
+ 'g / G : Go to first/last page',
28
+ 'Ctrl+B / Ctrl+F : Page up/down',
29
+ ],
30
+ position: { top: 'center', left: 'center' },
31
+ },
32
+ {
33
+ id: 'bookmarks',
34
+ title: '⭐ Bookmarks & Favorites',
35
+ content: [
36
+ 'Press \'f\' to toggle favorite on current session',
37
+ 'Press \'F\' to filter/show favorites only',
38
+ 'Favorites persist across restarts',
39
+ 'Access them quickly with the F filter',
40
+ ],
41
+ position: { top: 'center', left: 'center' },
42
+ },
43
+ {
44
+ id: 'widgets',
45
+ title: '📊 Widget Controls',
46
+ content: [
47
+ 'Use number keys 1-7 to toggle widgets',
48
+ 'Tab to cycle through widgets',
49
+ 'Resize terminal to adjust layout',
50
+ 'Widgets auto-refresh with live data',
51
+ ],
52
+ position: { top: 'center', left: 'center' },
53
+ },
54
+ {
55
+ id: 'actions',
56
+ title: '⚡ Quick Actions',
57
+ content: [
58
+ 'r : Refresh data immediately',
59
+ 's : Change sort mode',
60
+ 'e : Export session data',
61
+ 'd : View session details',
62
+ 'q : Quit dashboard',
63
+ ],
64
+ position: { top: 'center', left: 'center' },
65
+ },
66
+ ];
67
+
68
+ // Store dismissed hints per session
69
+ let dismissedHints = new Set();
70
+ let currentHintIndex = 0;
71
+ let hintOverlay = null;
72
+ let screenRef = null;
73
+
74
+ /**
75
+ * Check if hints should be shown (first run or explicit request)
76
+ * @param {Object} settings - Current dashboard settings
77
+ * @returns {boolean} - True if hints should be displayed
78
+ */
79
+ export function shouldShowHints(settings) {
80
+ return settings?.firstRun === true;
81
+ }
82
+
83
+ /**
84
+ * Mark first run as complete in settings
85
+ * @param {Object} settings - Current settings object
86
+ * @param {Function} saveSettingsFn - Function to save settings
87
+ */
88
+ export function markFirstRunComplete(settings, saveSettingsFn) {
89
+ if (settings && settings.firstRun) {
90
+ settings.firstRun = false;
91
+ if (typeof saveSettingsFn === 'function') {
92
+ saveSettingsFn(settings);
93
+ logger.info('First run hints marked as complete');
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Create a styled hint box with navigation controls
100
+ * @param {blessed.Screen} screen - The blessed screen
101
+ * @param {Object} hint - Hint configuration object
102
+ * @param {number} index - Current hint index
103
+ * @param {number} total - Total number of hints
104
+ * @returns {blessed.Box} - The hint box element
105
+ */
106
+ function createHintBox(screen, hint, index, total) {
107
+ const width = 50;
108
+ const height = 14;
109
+
110
+ // Main container
111
+ const container = blessed.box({
112
+ parent: screen,
113
+ top: 'center',
114
+ left: 'center',
115
+ width: width,
116
+ height: height,
117
+ border: { type: 'line', fg: 'brightCyan' },
118
+ style: {
119
+ bg: 'black',
120
+ border: { fg: 'brightCyan' },
121
+ },
122
+ tags: true,
123
+ shadow: true,
124
+ });
125
+
126
+ // Title bar
127
+ blessed.text({
128
+ parent: container,
129
+ top: 0,
130
+ left: 'center',
131
+ width: width - 2,
132
+ content: ` {bold}${hint.title}{/bold} `,
133
+ style: {
134
+ fg: 'brightCyan',
135
+ bg: 'black',
136
+ },
137
+ tags: true,
138
+ });
139
+
140
+ // Separator line
141
+ blessed.line({
142
+ parent: container,
143
+ top: 2,
144
+ left: 1,
145
+ right: 1,
146
+ orientation: 'horizontal',
147
+ style: { fg: 'dim' },
148
+ });
149
+
150
+ // Hint content
151
+ let contentY = 3;
152
+ hint.content.forEach((line) => {
153
+ blessed.text({
154
+ parent: container,
155
+ top: contentY++,
156
+ left: 2,
157
+ width: width - 4,
158
+ content: ` ${line}`,
159
+ style: {
160
+ fg: 'white',
161
+ bg: 'black',
162
+ },
163
+ });
164
+ });
165
+
166
+ // Footer separator
167
+ blessed.line({
168
+ parent: container,
169
+ top: height - 4,
170
+ left: 1,
171
+ right: 1,
172
+ orientation: 'horizontal',
173
+ style: { fg: 'dim' },
174
+ });
175
+
176
+ // Navigation help at bottom
177
+ const navText = index < total - 1
178
+ ? ' {bold}n{/bold}: Next {bold}q{/bold}: Skip all'
179
+ : ' {bold}q{/bold}: Close hints {bold}r{/bold}: Show again later';
180
+
181
+ blessed.text({
182
+ parent: container,
183
+ top: height - 3,
184
+ left: 'center',
185
+ width: width - 2,
186
+ content: navText,
187
+ style: {
188
+ fg: 'gray',
189
+ bg: 'black',
190
+ },
191
+ tags: true,
192
+ });
193
+
194
+ // Progress indicator
195
+ const progress = ` (${index + 1}/${total})`;
196
+ blessed.text({
197
+ parent: container,
198
+ top: height - 2,
199
+ left: 'center',
200
+ content: progress,
201
+ style: {
202
+ fg: 'dim',
203
+ bg: 'black',
204
+ },
205
+ });
206
+
207
+ return container;
208
+ }
209
+
210
+ /**
211
+ * Show the next hint in sequence
212
+ * @param {blessed.Screen} screen - The blessed screen
213
+ * @returns {boolean} - True if hint was shown, false if no more hints
214
+ */
215
+ function showNextHint(screen) {
216
+ // Clean up previous hint
217
+ if (hintOverlay) {
218
+ hintOverlay.destroy();
219
+ hintOverlay = null;
220
+ }
221
+
222
+ // Check if we've shown all hints
223
+ if (currentHintIndex >= HINTS.length) {
224
+ return false;
225
+ }
226
+
227
+ const hint = HINTS[currentHintIndex];
228
+ hintOverlay = createHintBox(screen, hint, currentHintIndex, HINTS.length);
229
+ screen.render();
230
+
231
+ return true;
232
+ }
233
+
234
+ /**
235
+ * Display first-run hint tooltips
236
+ * @param {blessed.Screen} screen - The blessed screen
237
+ * @param {Object} settings - Current settings
238
+ * @param {Function} saveSettingsFn - Function to save settings
239
+ * @returns {Promise<void>} - Resolves when all hints are dismissed
240
+ */
241
+ export async function showFirstRunHints(screen, settings, saveSettingsFn) {
242
+ if (!shouldShowHints(settings)) {
243
+ return;
244
+ }
245
+
246
+ screenRef = screen;
247
+ currentHintIndex = 0;
248
+ dismissedHints.clear();
249
+
250
+ return new Promise((resolve) => {
251
+ // Show first hint
252
+ showNextHint(screen);
253
+
254
+ // Set up key handlers for hint navigation
255
+ const keyHandler = (ch, key) => {
256
+ // 'n' or right arrow for next hint
257
+ if (ch === 'n' || ch === ' ' || key.name === 'right') {
258
+ currentHintIndex++;
259
+ if (!showNextHint(screen)) {
260
+ // No more hints, clean up
261
+ if (hintOverlay) {
262
+ hintOverlay.destroy();
263
+ hintOverlay = null;
264
+ }
265
+ screen.removeListener('keypress', keyHandler);
266
+ markFirstRunComplete(settings, saveSettingsFn);
267
+ screen.render();
268
+ resolve();
269
+ }
270
+ }
271
+
272
+ // 'q' or Escape to skip/close
273
+ if (ch === 'q' || key.name === 'escape') {
274
+ if (hintOverlay) {
275
+ hintOverlay.destroy();
276
+ hintOverlay = null;
277
+ }
278
+ screen.removeListener('keypress', keyHandler);
279
+ markFirstRunComplete(settings, saveSettingsFn);
280
+ screen.render();
281
+ resolve();
282
+ }
283
+
284
+ // 'r' to reset hints (shown on last hint)
285
+ if (ch === 'r' && currentHintIndex >= HINTS.length - 1) {
286
+ settings.firstRun = true;
287
+ if (typeof saveSettingsFn === 'function') {
288
+ saveSettingsFn(settings);
289
+ }
290
+ if (hintOverlay) {
291
+ hintOverlay.destroy();
292
+ hintOverlay = null;
293
+ }
294
+ screen.removeListener('keypress', keyHandler);
295
+ screen.render();
296
+ resolve();
297
+ }
298
+ };
299
+
300
+ screen.on('keypress', keyHandler);
301
+ });
302
+ }
303
+
304
+ /**
305
+ * Manually trigger hint display (for help menu)
306
+ * @param {blessed.Screen} screen - The blessed screen
307
+ * @returns {Promise<void>}
308
+ */
309
+ export async function showHintsManual(screen) {
310
+ screenRef = screen;
311
+ currentHintIndex = 0;
312
+ dismissedHints.clear();
313
+
314
+ return new Promise((resolve) => {
315
+ showNextHint(screen);
316
+
317
+ const keyHandler = (ch, key) => {
318
+ if (ch === 'n' || ch === ' ' || key.name === 'right') {
319
+ currentHintIndex++;
320
+ if (!showNextHint(screen)) {
321
+ if (hintOverlay) {
322
+ hintOverlay.destroy();
323
+ hintOverlay = null;
324
+ }
325
+ screen.removeListener('keypress', keyHandler);
326
+ screen.render();
327
+ resolve();
328
+ }
329
+ }
330
+
331
+ if (ch === 'q' || key.name === 'escape' || ch === 'h') {
332
+ if (hintOverlay) {
333
+ hintOverlay.destroy();
334
+ hintOverlay = null;
335
+ }
336
+ screen.removeListener('keypress', keyHandler);
337
+ screen.render();
338
+ resolve();
339
+ }
340
+ };
341
+
342
+ screen.on('keypress', keyHandler);
343
+ });
344
+ }
345
+
346
+ /**
347
+ * Dismiss any active hint overlay
348
+ */
349
+ export function dismissActiveHint() {
350
+ if (hintOverlay) {
351
+ hintOverlay.destroy();
352
+ hintOverlay = null;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Check if hints are currently being displayed
358
+ * @returns {boolean}
359
+ */
360
+ export function isShowingHints() {
361
+ return hintOverlay !== null;
362
+ }
363
+
364
+ export default {
365
+ shouldShowHints,
366
+ showFirstRunHints,
367
+ showHintsManual,
368
+ markFirstRunComplete,
369
+ dismissActiveHint,
370
+ isShowingHints,
371
+ };