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,311 @@
1
+ /**
2
+ * Plugin Errors Tests
3
+ * Tests for enhanced plugin error messages and diagnostics
4
+ */
5
+
6
+ import {
7
+ PluginError,
8
+ PluginErrorAnalyzer,
9
+ PLUGIN_ERROR_CODES,
10
+ formatPluginError,
11
+ extractErrorInfo,
12
+ } from '../src/plugin-errors.js';
13
+
14
+ describe('PluginError', () => {
15
+ test('creates PluginError with code and message', () => {
16
+ const error = new PluginError(
17
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
18
+ 'Plugin manifest not found',
19
+ { pluginId: 'test-plugin' }
20
+ );
21
+
22
+ expect(error.message).toBe('Plugin manifest not found');
23
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND);
24
+ expect(error.pluginId).toBe('test-plugin');
25
+ expect(error.name).toBe('PluginError');
26
+ });
27
+
28
+ test('provides suggestion based on error code', () => {
29
+ const error = new PluginError(
30
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
31
+ 'Manifest not found',
32
+ { pluginId: 'test-plugin' }
33
+ );
34
+
35
+ expect(error.suggestion).toContain('Create a plugin.json');
36
+ expect(error.getHint()).toContain('Create a plugin.json file');
37
+ });
38
+
39
+ test('provides documentation link when available', () => {
40
+ const error = new PluginError(
41
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
42
+ 'Manifest not found',
43
+ { pluginId: 'test-plugin' }
44
+ );
45
+
46
+ expect(error.docs).toContain('github.com');
47
+ });
48
+
49
+ test('getFormattedMessage includes all helpful information', () => {
50
+ const error = new PluginError(
51
+ PLUGIN_ERROR_CODES.MANIFEST_MISSING_FIELD,
52
+ 'Missing required field',
53
+ { pluginId: 'test-plugin' }
54
+ );
55
+
56
+ const formatted = error.getFormattedMessage();
57
+ expect(formatted).toContain('Plugin Error');
58
+ expect(formatted).toContain(PLUGIN_ERROR_CODES.MANIFEST_MISSING_FIELD);
59
+ expect(formatted).toContain('test-plugin');
60
+ expect(formatted).toContain('💡 Suggestion');
61
+ expect(formatted).toContain('📚 Documentation');
62
+ });
63
+
64
+ test('toJSON includes all properties', () => {
65
+ const error = new PluginError(
66
+ PLUGIN_ERROR_CODES.ENTRY_NOT_FOUND,
67
+ 'Entry point not found',
68
+ { pluginId: 'test-plugin', extra: 'detail' }
69
+ );
70
+
71
+ const json = error.toJSON();
72
+ expect(json.code).toBe(PLUGIN_ERROR_CODES.ENTRY_NOT_FOUND);
73
+ expect(json.pluginId).toBe('test-plugin');
74
+ expect(json.suggestion).toBeDefined();
75
+ expect(json.docs).toBeDefined();
76
+ });
77
+
78
+ test('handles error codes without full suggestions', () => {
79
+ const error = new PluginError(
80
+ 'UNKNOWN_CODE',
81
+ 'Unknown error',
82
+ { pluginId: 'test-plugin' }
83
+ );
84
+
85
+ expect(error.suggestion).toBe('Check the plugin documentation for more information');
86
+ expect(error.docs).toBeNull();
87
+ });
88
+
89
+ test('extracts pluginId from details', () => {
90
+ const error1 = new PluginError(
91
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
92
+ 'Error',
93
+ { pluginId: 'explicit-id' }
94
+ );
95
+ expect(error1.pluginId).toBe('explicit-id');
96
+
97
+ const error2 = new PluginError(
98
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
99
+ 'Error',
100
+ { id: 'id-from-details' }
101
+ );
102
+ expect(error2.pluginId).toBe('id-from-details');
103
+ });
104
+ });
105
+
106
+ describe('PluginErrorAnalyzer', () => {
107
+ describe('analyze', () => {
108
+ test('analyzes manifest JSON parse error', () => {
109
+ const originalError = new Error('Unexpected token } in JSON');
110
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {
111
+ phase: 'manifest',
112
+ });
113
+
114
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON);
115
+ expect(error.message).toContain('test-plugin');
116
+ expect(error.suggestion).toContain('JSON');
117
+ });
118
+
119
+ test('analyzes manifest not found error', () => {
120
+ const originalError = new Error('ENOENT: file not found');
121
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {
122
+ phase: 'manifest',
123
+ });
124
+
125
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND);
126
+ });
127
+
128
+ test('analyzes entry point export error', () => {
129
+ const originalError = new Error('does not provide an export');
130
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {
131
+ phase: 'entry',
132
+ });
133
+
134
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.ENTRY_NO_EXPORT);
135
+ });
136
+
137
+ test('analyzes widget method missing error', () => {
138
+ const originalError = new Error('missing required methods: render, getData');
139
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {
140
+ phase: 'widget',
141
+ });
142
+
143
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.WIDGET_MISSING_METHODS);
144
+ });
145
+
146
+ test('analyzes path validation error', () => {
147
+ const originalError = new Error('invalid path: traversal detected');
148
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {});
149
+
150
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.PATH_INVALID);
151
+ });
152
+
153
+ test('analyzes dependency missing error', () => {
154
+ const originalError = new Error('dependency not found: other-plugin');
155
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {});
156
+
157
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.DEPENDENCY_MISSING);
158
+ });
159
+
160
+ test('analyzes circular dependency error', () => {
161
+ const originalError = new Error('circular dependency detected');
162
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {});
163
+
164
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.DEPENDENCY_CIRCULAR);
165
+ });
166
+
167
+ test('falls back to generic error code for unknown errors', () => {
168
+ const originalError = new Error('some random error');
169
+ const error = PluginErrorAnalyzer.analyze(originalError, 'test-plugin', {});
170
+
171
+ expect(error.code).toBe(PLUGIN_ERROR_CODES.PLUGIN_LOAD_ERROR);
172
+ });
173
+ });
174
+
175
+ describe('checkCommonMistakes', () => {
176
+ test('detects missing super() call', () => {
177
+ const error = new Error('Error in constructor');
178
+ error.stack = 'Error\n at new MyWidget (file.js:5:5)\n at super call';
179
+
180
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
181
+ // Note: the actual detection depends on stack trace content
182
+ expect(result).toBeNull(); // Since our mock stack doesn't contain actual 'super' keyword
183
+ });
184
+
185
+ test('detects missing module', () => {
186
+ const error = new Error("Cannot find module 'some-package'");
187
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
188
+
189
+ expect(result).toBeTruthy();
190
+ expect(result.mistake).toBe('Missing import/module');
191
+ expect(result.fix).toContain('npm install');
192
+ });
193
+
194
+ test('detects calling non-function', () => {
195
+ const error = new Error('foo is not a function');
196
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
197
+
198
+ expect(result).toBeTruthy();
199
+ expect(result.mistake).toBe('Calling a non-function');
200
+ });
201
+
202
+ test('detects undefined property access', () => {
203
+ const error = new Error("Cannot read property 'foo' of undefined");
204
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
205
+
206
+ expect(result).toBeTruthy();
207
+ expect(result.mistake).toContain('undefined');
208
+ expect(result.fix).toContain('?.');
209
+ });
210
+
211
+ test('detects trailing comma in JSON', () => {
212
+ const error = new Error('Unexpected token } in JSON');
213
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
214
+
215
+ expect(result).toBeTruthy();
216
+ expect(result.mistake).toContain('Trailing comma');
217
+ });
218
+
219
+ test('detects invalid JSON syntax', () => {
220
+ const error = new Error('Unexpected token in JSON at position 42');
221
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
222
+
223
+ expect(result).toBeTruthy();
224
+ expect(result.mistake).toContain('JSON');
225
+ });
226
+
227
+ test('returns null for unrecognized errors', () => {
228
+ const error = new Error('completely unexpected error');
229
+ const result = PluginErrorAnalyzer.checkCommonMistakes(error);
230
+
231
+ expect(result).toBeNull();
232
+ });
233
+ });
234
+ });
235
+
236
+ describe('formatPluginError', () => {
237
+ test('returns formatted message by default', () => {
238
+ const error = new PluginError(
239
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
240
+ 'Test error',
241
+ { pluginId: 'test' }
242
+ );
243
+
244
+ const formatted = formatPluginError(error);
245
+ expect(formatted).toContain('Plugin Error');
246
+ expect(formatted).toContain('💡 Suggestion');
247
+ });
248
+
249
+ test('returns compact message when requested', () => {
250
+ const error = new PluginError(
251
+ PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND,
252
+ 'Test error',
253
+ { pluginId: 'test' }
254
+ );
255
+
256
+ const formatted = formatPluginError(error, { compact: true });
257
+ expect(formatted).toContain(PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND);
258
+ expect(formatted).not.toContain('💡 Suggestion'); // Compact doesn't include full format
259
+ });
260
+ });
261
+
262
+ describe('extractErrorInfo', () => {
263
+ test('extracts info from PluginError', () => {
264
+ const error = new PluginError(
265
+ PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON,
266
+ 'Test error',
267
+ { pluginId: 'test' }
268
+ );
269
+
270
+ const info = extractErrorInfo(error);
271
+ expect(info.isPluginError).toBe(true);
272
+ expect(info.code).toBe(PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON);
273
+ expect(info.pluginId).toBe('test');
274
+ expect(info.suggestion).toBeDefined();
275
+ expect(info.docs).toBeDefined();
276
+ expect(info.hasFix).toBe(true);
277
+ expect(info.formatted).toBeDefined();
278
+ });
279
+
280
+ test('extracts info from regular Error', () => {
281
+ const error = new Error('regular error');
282
+
283
+ const info = extractErrorInfo(error);
284
+ expect(info.isPluginError).toBe(false);
285
+ expect(info.message).toBe('regular error');
286
+ expect(info.commonMistake).toBeNull();
287
+ });
288
+
289
+ test('analyzes common mistakes for regular errors', () => {
290
+ const error = new Error('Cannot find module');
291
+
292
+ const info = extractErrorInfo(error);
293
+ expect(info.isPluginError).toBe(false);
294
+ expect(info.commonMistake).toBeTruthy();
295
+ expect(info.commonMistake.mistake).toBe('Missing import/module');
296
+ });
297
+ });
298
+
299
+ describe('PLUGIN_ERROR_CODES', () => {
300
+ test('contains all expected error codes', () => {
301
+ expect(PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND).toBe('PLUGIN_MANIFEST_NOT_FOUND');
302
+ expect(PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON).toBe('PLUGIN_MANIFEST_INVALID_JSON');
303
+ expect(PLUGIN_ERROR_CODES.MANIFEST_MISSING_FIELD).toBe('PLUGIN_MANIFEST_MISSING_FIELD');
304
+ expect(PLUGIN_ERROR_CODES.ENTRY_NOT_FOUND).toBe('PLUGIN_ENTRY_NOT_FOUND');
305
+ expect(PLUGIN_ERROR_CODES.ENTRY_NO_EXPORT).toBe('PLUGIN_ENTRY_NO_EXPORT');
306
+ expect(PLUGIN_ERROR_CODES.WIDGET_MISSING_METHODS).toBe('PLUGIN_WIDGET_MISSING_METHODS');
307
+ expect(PLUGIN_ERROR_CODES.PATH_INVALID).toBe('PLUGIN_PATH_INVALID');
308
+ expect(PLUGIN_ERROR_CODES.DEPENDENCY_MISSING).toBe('PLUGIN_DEPENDENCY_MISSING');
309
+ expect(PLUGIN_ERROR_CODES.PLUGIN_LOAD_ERROR).toBe('PLUGIN_LOAD_ERROR');
310
+ });
311
+ });