claw-dashboard 1.9.0 → 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 +5236 -566
  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
package/src/cache.js ADDED
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Cache module with TTL support for system metrics
3
+ * Provides caching for expensive systeminformation calls
4
+ * Supports worker threads for heavy operations
5
+ */
6
+
7
+ import config from './config.js';
8
+ import logger from './logger.js';
9
+
10
+ // Worker pool for heavy operations (lazy-loaded)
11
+ let workerPool = null;
12
+
13
+ /**
14
+ * Get the worker pool instance (lazy initialization)
15
+ * @returns {Object|null} Worker pool instance or null if not available
16
+ */
17
+ async function getWorkerPool() {
18
+ if (!config.WORKERS?.ENABLED) {
19
+ return null;
20
+ }
21
+
22
+ if (!workerPool) {
23
+ try {
24
+ const { default: pool } = await import('./workers/worker-pool.js');
25
+ workerPool = pool;
26
+ } catch (error) {
27
+ logger.debug('Worker pool not available:', error.message);
28
+ return null;
29
+ }
30
+ }
31
+
32
+ return workerPool;
33
+ }
34
+
35
+ // In-memory cache store
36
+ const cache = new Map();
37
+
38
+ /**
39
+ * Cache configuration for different data types
40
+ */
41
+ // Use CACHE_CONFIG from config.js
42
+
43
+ /**
44
+ * Get a cached value if still valid
45
+ * @param {string} key - Cache key
46
+ * @returns {any|null} Cached value or null if expired/missing
47
+ */
48
+ export function get(key) {
49
+ const entry = cache.get(key);
50
+ if (!entry) return null;
51
+
52
+ if (Date.now() > entry.expiresAt) {
53
+ cache.delete(key);
54
+ return null;
55
+ }
56
+
57
+ return entry.value;
58
+ }
59
+
60
+ /**
61
+ * Set a cached value with TTL
62
+ * @param {string} key - Cache key
63
+ * @param {any} value - Value to cache
64
+ * @param {number} ttl - Time to live in milliseconds (optional, uses config default)
65
+ */
66
+ export function set(key, value, ttl) {
67
+ const cacheTtlConfig = config.CACHE_CONFIG[key] || { ttl: config.CACHE_TTL.DEFAULT };
68
+ const actualTtl = ttl || cacheTtlConfig.ttl;
69
+
70
+ cache.set(key, {
71
+ value,
72
+ expiresAt: Date.now() + actualTtl,
73
+ createdAt: Date.now(),
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Get or fetch data with caching
79
+ * @param {string} key - Cache key
80
+ * @param {Function} fetcher - Async function to fetch data if cache miss
81
+ * @param {number} ttl - Optional TTL override
82
+ * @returns {Promise<any>} Cached or fresh data
83
+ */
84
+ export async function getOrFetch(key, fetcher, ttl) {
85
+ const cached = get(key);
86
+ if (cached !== null) {
87
+ return cached;
88
+ }
89
+
90
+ const data = await fetcher();
91
+ set(key, data, ttl);
92
+ return data;
93
+ }
94
+
95
+ /**
96
+ * Execute a systeminformation command via worker thread or fallback
97
+ * @param {string} command - Worker command name
98
+ * @param {Function} fallbackFn - Fallback function for direct execution
99
+ * @returns {Promise<any>} Command result
100
+ */
101
+ async function executeWithWorker(command, fallbackFn) {
102
+ const pool = await getWorkerPool();
103
+ if (pool) {
104
+ try {
105
+ return await pool.execute(command);
106
+ } catch (error) {
107
+ logger.debug(`Worker execution failed for ${command}, using fallback: ${error.message}`);
108
+ }
109
+ }
110
+ return fallbackFn();
111
+ }
112
+
113
+ /**
114
+ * Get cached CPU data or fetch fresh
115
+ * Uses worker threads when available to avoid blocking the UI
116
+ * @returns {Promise<Object>} CPU load data
117
+ */
118
+ export async function getCpuData() {
119
+ return getOrFetch('cpu', async () => {
120
+ try {
121
+ // Add 2 second timeout for CPU data
122
+ const timeoutMs = 2000;
123
+ const fetchPromise = executeWithWorker('currentLoad', async () => {
124
+ const si = await import('systeminformation');
125
+ return await si.currentLoad();
126
+ });
127
+ const timeoutPromise = new Promise((_, reject) =>
128
+ setTimeout(() => reject(new Error('CPU data timeout')), timeoutMs)
129
+ );
130
+ return await Promise.race([fetchPromise, timeoutPromise]);
131
+ } catch (e) {
132
+ logger.warn(`systeminformation.currentLoad() failed: ${e.message}`);
133
+ throw e;
134
+ }
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Get cached memory data or fetch fresh
140
+ * Uses worker threads when available to avoid blocking the UI
141
+ * @returns {Promise<Object>} Memory data
142
+ */
143
+ export async function getMemoryData() {
144
+ return getOrFetch('memory', async () => {
145
+ try {
146
+ // Add 2 second timeout for memory data
147
+ const timeoutMs = 2000;
148
+ const fetchPromise = executeWithWorker('mem', async () => {
149
+ const si = await import('systeminformation');
150
+ return await si.mem();
151
+ });
152
+ const timeoutPromise = new Promise((_, reject) =>
153
+ setTimeout(() => reject(new Error('Memory data timeout')), timeoutMs)
154
+ );
155
+ return await Promise.race([fetchPromise, timeoutPromise]);
156
+ } catch (e) {
157
+ logger.warn(`systeminformation.mem() failed: ${e.message}`);
158
+ throw e;
159
+ }
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Get cached GPU data or fetch fresh
165
+ * Uses worker threads when available to avoid blocking the UI
166
+ * @returns {Promise<Object>} GPU/graphics data
167
+ */
168
+ export async function getGpuData() {
169
+ return getOrFetch('gpu', async () => {
170
+ try {
171
+ return await executeWithWorker('graphics', async () => {
172
+ const si = await import('systeminformation');
173
+ return await si.graphics();
174
+ });
175
+ } catch (e) {
176
+ logger.warn(`systeminformation.graphics() failed: ${e.message}`);
177
+ throw e;
178
+ }
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Get cached network data or fetch fresh
184
+ * Uses worker threads when available to avoid blocking the UI
185
+ * Falls back to empty array on timeout (non-critical data)
186
+ * @returns {Promise<Object>} Network stats data
187
+ */
188
+ export async function getNetworkData() {
189
+ return getOrFetch('network', async () => {
190
+ try {
191
+ // Add timeout wrapper for network stats
192
+ const timeoutMs = 2000; // 2 second timeout
193
+ const fetchPromise = executeWithWorker('networkStats', async () => {
194
+ const si = await import('systeminformation');
195
+ return await si.networkStats();
196
+ });
197
+
198
+ const timeoutPromise = new Promise((_, reject) =>
199
+ setTimeout(() => reject(new Error('Network stats timeout')), timeoutMs)
200
+ );
201
+
202
+ return await Promise.race([fetchPromise, timeoutPromise]);
203
+ } catch (e) {
204
+ logger.warn(`Network data fetch failed or timed out: ${e.message}`);
205
+ // Return empty array - network stats are nice-to-have, not critical
206
+ return [];
207
+ }
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Get cached disk data or fetch fresh
213
+ * Uses worker threads when available to avoid blocking the UI
214
+ * @returns {Promise<Object>} Disk size data
215
+ */
216
+ export async function getDiskData() {
217
+ return getOrFetch('disk', async () => {
218
+ try {
219
+ return await executeWithWorker('fsSize', async () => {
220
+ const si = await import('systeminformation');
221
+ return await si.fsSize();
222
+ });
223
+ } catch (e) {
224
+ logger.warn(`systeminformation.fsSize() failed: ${e.message}`);
225
+ throw e;
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Get cached system info data or fetch fresh
232
+ * Uses native os module and short-circuit for speed
233
+ * System info rarely changes, so we cache aggressively
234
+ * @returns {Promise<Object>} System info (osInfo, versions, time)
235
+ */
236
+ export async function getSystemData() {
237
+ return getOrFetch('system', async () => {
238
+ try {
239
+ // Use native os module for faster results
240
+ const os = await import('os');
241
+
242
+ // Build system data from native os module (much faster than systeminformation)
243
+ const systemData = {
244
+ os: {
245
+ platform: os.platform(),
246
+ distro: os.type(),
247
+ release: os.release(),
248
+ arch: os.arch(),
249
+ },
250
+ ver: {
251
+ node: process.version,
252
+ },
253
+ time: {
254
+ uptime: os.uptime(),
255
+ }
256
+ };
257
+
258
+ // Try to get more detailed info via worker (non-blocking)
259
+ // This runs in background and updates cache if successful
260
+ executeWithWorker('systemData', async () => {
261
+ const si = await import('systeminformation');
262
+ const [osInfo, versions, time] = await Promise.all([
263
+ si.osInfo(),
264
+ si.versions(),
265
+ si.time(),
266
+ ]);
267
+ return { os: osInfo, ver: versions, time };
268
+ }).then(detailedData => {
269
+ // Update cache with detailed data in background
270
+ set('system', detailedData);
271
+ }).catch(() => {
272
+ // Ignore errors - we already have basic data
273
+ });
274
+
275
+ return systemData;
276
+ } catch (e) {
277
+ logger.warn(`System data fetch failed: ${e.message}`);
278
+ throw e;
279
+ }
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Force refresh a specific cache entry
285
+ * @param {string} key - Cache key to refresh
286
+ */
287
+ export function invalidate(key) {
288
+ cache.delete(key);
289
+ }
290
+
291
+ /**
292
+ * Clear all cache entries
293
+ */
294
+ export function clear() {
295
+ cache.clear();
296
+ }
297
+
298
+ /**
299
+ * Get cache status for debugging
300
+ * @returns {Object} Cache status info
301
+ */
302
+ export function getStatus() {
303
+ const now = Date.now();
304
+ const status = {};
305
+
306
+ for (const [key, entry] of cache) {
307
+ const remaining = Math.max(0, entry.expiresAt - now);
308
+ status[key] = {
309
+ cached: true,
310
+ age: now - entry.createdAt,
311
+ ttlRemaining: remaining,
312
+ configTtl: config.CACHE_CONFIG[key]?.ttl || config.CACHE_TTL.DEFAULT,
313
+ };
314
+ }
315
+
316
+ return status;
317
+ }
318
+
319
+ /**
320
+ * Debounce utility for rapid key presses
321
+ * @param {Function} fn - Function to debounce
322
+ * @param {number} delay - Delay in milliseconds
323
+ * @returns {Function} Debounced function
324
+ */
325
+ export function debounce(fn, delay) {
326
+ let timeoutId = null;
327
+ let lastArgs = null;
328
+
329
+ return function(...args) {
330
+ lastArgs = args;
331
+
332
+ if (timeoutId) {
333
+ clearTimeout(timeoutId);
334
+ }
335
+
336
+ timeoutId = setTimeout(() => {
337
+ fn.apply(this, lastArgs);
338
+ timeoutId = null;
339
+ }, delay);
340
+ };
341
+ }
342
+
343
+ /**
344
+ * Throttle utility for rate limiting
345
+ * @param {Function} fn - Function to throttle
346
+ * @param {number} limit - Minimum interval between calls in ms
347
+ * @returns {Function} Throttled function
348
+ */
349
+ export function throttle(fn, limit) {
350
+ let lastCall = 0;
351
+ let timeoutId = null;
352
+
353
+ return function(...args) {
354
+ const now = Date.now();
355
+ const remaining = limit - (now - lastCall);
356
+
357
+ if (remaining <= 0) {
358
+ if (timeoutId) {
359
+ clearTimeout(timeoutId);
360
+ timeoutId = null;
361
+ }
362
+ lastCall = now;
363
+ fn.apply(this, args);
364
+ } else if (!timeoutId) {
365
+ timeoutId = setTimeout(() => {
366
+ lastCall = Date.now();
367
+ timeoutId = null;
368
+ fn.apply(this, args);
369
+ }, remaining);
370
+ }
371
+ };
372
+ }
373
+
374
+ export default {
375
+ get,
376
+ set,
377
+ getOrFetch,
378
+ getCpuData,
379
+ getMemoryData,
380
+ getGpuData,
381
+ getNetworkData,
382
+ getDiskData,
383
+ getSystemData,
384
+ invalidate,
385
+ clear,
386
+ getStatus,
387
+ debounce,
388
+ throttle,
389
+ CACHE_CONFIG: config.CACHE_CONFIG,
390
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Checksum verification utilities for OpenClaw gateway responses
3
+ * Provides integrity verification for data received from gateway endpoints
4
+ */
5
+
6
+ import crypto from 'crypto';
7
+ import config from './config.js';
8
+ import logger from './logger.js';
9
+
10
+ /**
11
+ * Supported hash algorithms
12
+ * @typedef {'sha256' | 'sha512' | 'md5'} HashAlgorithm
13
+ */
14
+
15
+ const SUPPORTED_ALGORITHMS = ['sha256', 'sha512', 'md5'];
16
+
17
+ /**
18
+ * Compute checksum of data using specified algorithm
19
+ * @param {string|Buffer} data - Data to compute checksum for
20
+ * @param {HashAlgorithm} [algorithm] - Hash algorithm to use (defaults to config)
21
+ * @returns {string} - Computed checksum in hex format
22
+ */
23
+ export function computeChecksum(data, algorithm = null) {
24
+ const algo = algorithm || config.CHECKSUM.ALGORITHM;
25
+
26
+ if (!SUPPORTED_ALGORITHMS.includes(algo)) {
27
+ throw new Error(`Unsupported hash algorithm: ${algo}. Supported: ${SUPPORTED_ALGORITHMS.join(', ')}`);
28
+ }
29
+
30
+ const hash = crypto.createHash(algo);
31
+ hash.update(data);
32
+ return hash.digest('hex');
33
+ }
34
+
35
+ /**
36
+ * Verify response checksum against computed checksum
37
+ * @param {string|Buffer} data - Response body data
38
+ * @param {string} expectedChecksum - Expected checksum from header
39
+ * @param {HashAlgorithm} [algorithm] - Hash algorithm to use
40
+ * @returns {boolean} - True if checksums match
41
+ */
42
+ export function verifyChecksum(data, expectedChecksum, algorithm = null) {
43
+ if (!expectedChecksum) {
44
+ return false;
45
+ }
46
+
47
+ try {
48
+ const computed = computeChecksum(data, algorithm);
49
+ // Use timing-safe comparison to prevent timing attacks
50
+ return crypto.timingSafeEqual(
51
+ Buffer.from(computed, 'hex'),
52
+ Buffer.from(expectedChecksum, 'hex')
53
+ );
54
+ } catch (err) {
55
+ logger.debug(`Checksum verification failed: ${err.message}`);
56
+ return false;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Verify HTTP response checksum from header
62
+ * @param {http.IncomingMessage} response - HTTP response object
63
+ * @param {string} responseBody - Response body as string
64
+ * @returns {{verified: boolean, checksum: string|null, error: string|null}} - Verification result
65
+ */
66
+ export function verifyResponseChecksum(response, responseBody) {
67
+ if (!config.CHECKSUM.ENABLED) {
68
+ return { verified: true, checksum: null, error: null };
69
+ }
70
+
71
+ const headerName = config.CHECKSUM.HEADER_NAME.toLowerCase();
72
+ const expectedChecksum = response.headers[headerName];
73
+
74
+ // Check if response has checksum header
75
+ if (!expectedChecksum) {
76
+ if (config.CHECKSUM.STRICT_MODE) {
77
+ return {
78
+ verified: false,
79
+ checksum: null,
80
+ error: `Missing ${config.CHECKSUM.HEADER_NAME} header (strict mode enabled)`
81
+ };
82
+ }
83
+ // Non-strict mode: accept responses without checksum
84
+ return { verified: true, checksum: null, error: null };
85
+ }
86
+
87
+ // Validate checksum format (hex string)
88
+ const checksumStr = Array.isArray(expectedChecksum) ? expectedChecksum[0] : expectedChecksum;
89
+ if (!/^[a-f0-9]+$/i.test(checksumStr)) {
90
+ return {
91
+ verified: false,
92
+ checksum: checksumStr,
93
+ error: 'Invalid checksum format: expected hex string'
94
+ };
95
+ }
96
+
97
+ // Verify checksum
98
+ const isValid = verifyChecksum(responseBody, checksumStr);
99
+
100
+ if (!isValid) {
101
+ return {
102
+ verified: false,
103
+ checksum: checksumStr,
104
+ error: 'Checksum mismatch: computed checksum does not match header'
105
+ };
106
+ }
107
+
108
+ return { verified: true, checksum: checksumStr, error: null };
109
+ }
110
+
111
+ /**
112
+ * Get checksum metadata for logging/debugging
113
+ * @param {http.IncomingMessage} response - HTTP response object
114
+ * @returns {Object} - Checksum metadata
115
+ */
116
+ export function getChecksumMetadata(response) {
117
+ const headerName = config.CHECKSUM.HEADER_NAME.toLowerCase();
118
+ const checksum = response.headers[headerName];
119
+
120
+ return {
121
+ algorithm: config.CHECKSUM.ALGORITHM,
122
+ headerName: config.CHECKSUM.HEADER_NAME,
123
+ headerPresent: !!checksum,
124
+ checksum: checksum || null,
125
+ strictMode: config.CHECKSUM.STRICT_MODE,
126
+ enabled: config.CHECKSUM.ENABLED,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Check if an error is a checksum verification failure
132
+ * @param {Error} error - Error to check
133
+ * @returns {boolean} - True if checksum verification error
134
+ */
135
+ export function isChecksumError(error) {
136
+ return error && error.code === 'CHECKSUM_ERROR';
137
+ }
138
+
139
+ export default {
140
+ computeChecksum,
141
+ verifyChecksum,
142
+ verifyResponseChecksum,
143
+ getChecksumMetadata,
144
+ isChecksumError,
145
+ SUPPORTED_ALGORITHMS,
146
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CLI Argument Parsing Module
3
+ * Handles parsing of command-line arguments for Claw Dashboard
4
+ */
5
+
6
+ import config from '../config.js';
7
+
8
+ /**
9
+ * Parse command-line arguments
10
+ * @returns {Object} Parsed CLI options
11
+ */
12
+ export function parseCliArgs() {
13
+ const args = process.argv.slice(2);
14
+ const options = {
15
+ help: false,
16
+ version: false,
17
+ debug: false,
18
+ web: false,
19
+ webPort: config.WEB.DEFAULT_PORT,
20
+ webHost: config.WEB.HOST,
21
+ watch: false,
22
+ watchPlugins: false,
23
+ command: null,
24
+ commandArgs: [],
25
+ };
26
+
27
+ // Check for commands first
28
+ if (args.length > 0 && !args[0].startsWith('-')) {
29
+ const firstArg = args[0];
30
+ if (firstArg === 'create-plugin') {
31
+ options.command = 'create-plugin';
32
+ options.commandArgs = args.slice(1);
33
+ return options;
34
+ }
35
+ if (firstArg === 'validate-plugin') {
36
+ options.command = 'validate-plugin';
37
+ options.commandArgs = args.slice(1);
38
+ return options;
39
+ }
40
+ if (firstArg === 'validate-config') {
41
+ options.command = 'validate-config';
42
+ options.commandArgs = args.slice(1);
43
+ return options;
44
+ }
45
+ if (firstArg === 'export-snapshot') {
46
+ options.command = 'export-snapshot';
47
+ options.commandArgs = args.slice(1);
48
+ return options;
49
+ }
50
+ if (firstArg === 'import-snapshot') {
51
+ options.command = 'import-snapshot';
52
+ options.commandArgs = args.slice(1);
53
+ return options;
54
+ }
55
+ if (firstArg === 'list-templates') {
56
+ options.command = 'list-templates';
57
+ options.commandArgs = args.slice(1);
58
+ return options;
59
+ }
60
+ }
61
+
62
+ for (let i = 0; i < args.length; i++) {
63
+ const arg = args[i];
64
+ switch (arg) {
65
+ case '-h':
66
+ case '--help':
67
+ options.help = true;
68
+ break;
69
+ case '-v':
70
+ case '--version':
71
+ options.version = true;
72
+ break;
73
+ case '-d':
74
+ case '--debug':
75
+ options.debug = true;
76
+ break;
77
+ case '-w':
78
+ case '--web':
79
+ options.web = true;
80
+ break;
81
+ case '-p':
82
+ case '--web-port':
83
+ options.web = true;
84
+ if (i + 1 < args.length) {
85
+ const port = parseInt(args[++i], 10);
86
+ if (!isNaN(port) && port > 0 && port < 65536) {
87
+ options.webPort = port;
88
+ }
89
+ }
90
+ break;
91
+ case '--web-host':
92
+ options.web = true;
93
+ if (i + 1 < args.length) {
94
+ options.webHost = args[++i];
95
+ }
96
+ break;
97
+ case '-W':
98
+ case '--watch':
99
+ options.watch = true;
100
+ break;
101
+ case '--watch-plugins':
102
+ options.watchPlugins = true;
103
+ break;
104
+ }
105
+ }
106
+
107
+ return options;
108
+ }
109
+
110
+ export default { parseCliArgs };