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.
- package/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5285 -566
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -6
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1934 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1056 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- 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
|
+
};
|