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,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ESBuild configuration for claw-dashboard
5
+ * Bundles the application for distribution
6
+ */
7
+
8
+ import * as esbuild from 'esbuild';
9
+ import { readFileSync, writeFileSync } from 'fs';
10
+ import { dirname, join } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ /**
17
+ * Get current version from package.json
18
+ * @returns {string} Current version
19
+ */
20
+ function getVersion() {
21
+ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
22
+ return pkg.version;
23
+ }
24
+
25
+ /**
26
+ * Create banner with version info
27
+ * @returns {string} Banner comment
28
+ */
29
+ function createBanner() {
30
+ const version = getVersion();
31
+ return `#!/usr/bin/env node
32
+ /**
33
+ * claw-dashboard v${version}
34
+ * A beautiful console dashboard for monitoring OpenClaw instances
35
+ *
36
+ * License: MIT
37
+ * Repository: https://github.com/spleck/claw-dashboard
38
+ */
39
+ `;
40
+ }
41
+
42
+ /**
43
+ * Build configuration
44
+ */
45
+ const buildConfig = {
46
+ entryPoints: ['index.js'],
47
+ bundle: true,
48
+ platform: 'node',
49
+ target: 'node18',
50
+ outfile: 'dist/clawdash',
51
+ format: 'esm',
52
+ banner: {
53
+ js: createBanner(),
54
+ },
55
+ // External dependencies that should not be bundled
56
+ // (native modules, large dependencies, or things with dynamic requires)
57
+ external: [
58
+ 'blessed',
59
+ 'blessed-contrib',
60
+ 'systeminformation',
61
+ ],
62
+ minify: true,
63
+ sourcemap: false,
64
+ metafile: true,
65
+ logLevel: 'info',
66
+ };
67
+
68
+ /**
69
+ * Development build configuration
70
+ */
71
+ const devConfig = {
72
+ ...buildConfig,
73
+ minify: false,
74
+ sourcemap: true,
75
+ outfile: 'dist/clawdash.dev',
76
+ };
77
+
78
+ /**
79
+ * Run build
80
+ * @param {Object} config - Build configuration
81
+ * @param {string} mode - Build mode (production or development)
82
+ */
83
+ async function build(config, mode = 'production') {
84
+ console.log(`Building for ${mode}...`);
85
+ console.log(`Version: ${getVersion()}`);
86
+
87
+ try {
88
+ const result = await esbuild.build({
89
+ ...config,
90
+ write: false,
91
+ });
92
+
93
+ // Add shebang if not present and make executable
94
+ let output = result.outputFiles[0].text;
95
+ if (!output.startsWith('#!')) {
96
+ output = '#!/usr/bin/env node\n' + output;
97
+ }
98
+
99
+ // Write the output file
100
+ writeFileSync(config.outfile, output, { mode: 0o755 });
101
+
102
+ // Write metafile for analysis
103
+ if (result.metafile) {
104
+ writeFileSync(
105
+ config.outfile + '.meta.json',
106
+ JSON.stringify(result.metafile, null, 2)
107
+ );
108
+ }
109
+
110
+ // Calculate bundle size
111
+ const bytes = Buffer.byteLength(output, 'utf8');
112
+ const kb = (bytes / 1024).toFixed(2);
113
+
114
+ console.log(`✓ Build successful: ${config.outfile} (${kb} KB)`);
115
+
116
+ // Print bundle analysis
117
+ if (result.metafile) {
118
+ const outputs = result.metafile.outputs;
119
+ const outputKey = Object.keys(outputs).find(k => k.endsWith('.js') || k.endsWith('clawdash'));
120
+ if (outputKey) {
121
+ const output = outputs[outputKey];
122
+ console.log(` - Bundle size: ${(output.bytes / 1024).toFixed(2)} KB`);
123
+ console.log(` - Exports: ${output.exports.join(', ')}`);
124
+ }
125
+ }
126
+
127
+ return true;
128
+ } catch (error) {
129
+ console.error('✗ Build failed:', error.message);
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Main execution
136
+ */
137
+ async function main() {
138
+ const args = process.argv.slice(2);
139
+ const isDev = args.includes('--dev') || args.includes('-d');
140
+ const shouldAnalyze = args.includes('--analyze') || args.includes('-a');
141
+
142
+ const config = isDev ? devConfig : buildConfig;
143
+
144
+ const success = await build(config, isDev ? 'development' : 'production');
145
+
146
+ if (success && shouldAnalyze) {
147
+ console.log('\nBundle analysis written to:', config.outfile + '.meta.json');
148
+ }
149
+
150
+ process.exit(success ? 0 : 1);
151
+ }
152
+
153
+ // Run if called directly
154
+ if (import.meta.url === `file://${process.argv[1]}`) {
155
+ main();
156
+ }
157
+
158
+ export { build, buildConfig, devConfig };
@@ -0,0 +1,56 @@
1
+ import js from '@eslint/js';
2
+
3
+ export default [
4
+ js.configs.recommended,
5
+ {
6
+ files: ['src/**/*.js'],
7
+ ignores: ['src/workers/*.js'],
8
+ languageOptions: {
9
+ ecmaVersion: 2022,
10
+ sourceType: 'module',
11
+ globals: {
12
+ console: 'readonly',
13
+ process: 'readonly',
14
+ setTimeout: 'readonly',
15
+ setInterval: 'readonly',
16
+ clearInterval: 'readonly',
17
+ clearTimeout: 'readonly',
18
+ setImmediate: 'readonly',
19
+ clearImmediate: 'readonly',
20
+ Buffer: 'readonly',
21
+ __dirname: 'readonly',
22
+ __filename: 'readonly',
23
+ module: 'readonly',
24
+ require: 'readonly',
25
+ exports: 'readonly',
26
+ global: 'readonly'
27
+ }
28
+ },
29
+ rules: {
30
+ 'no-unused-vars': 'off',
31
+ 'no-empty': 'off',
32
+ 'no-control-regex': 'off',
33
+ 'no-undef': 'off',
34
+ 'semi': ['error', 'always'],
35
+ 'quotes': ['error', 'single'],
36
+ 'prefer-const': 'off',
37
+ 'no-var': 'off',
38
+ 'preserve-caught-error': 'off'
39
+ }
40
+ },
41
+ {
42
+ files: ['tests/**/*.js'],
43
+ languageOptions: {
44
+ globals: {
45
+ describe: 'readonly',
46
+ test: 'readonly',
47
+ expect: 'readonly',
48
+ beforeEach: 'readonly',
49
+ afterEach: 'readonly',
50
+ beforeAll: 'readonly',
51
+ afterAll: 'readonly',
52
+ jest: 'readonly'
53
+ }
54
+ }
55
+ }
56
+ ];
@@ -0,0 +1,122 @@
1
+ # Plugin Examples
2
+
3
+ This directory contains example plugins demonstrating the Claw Dashboard widget plugin system.
4
+
5
+ ## Available Examples
6
+
7
+ ### hello-world
8
+
9
+ The simplest possible widget example demonstrating:
10
+ - Basic widget structure extending BaseWidget
11
+ - Required lifecycle methods (init, create, getData, render, destroy)
12
+ - Simple configuration handling
13
+ - Basic blessed UI creation
14
+
15
+ ### weather-widget
16
+
17
+ A weather widget that demonstrates:
18
+ - Plugin manifest structure
19
+ - Widget class extending BaseWidget
20
+ - UI creation with blessed
21
+ - Simulated data fetching and rendering
22
+ - Configuration handling with defaults
23
+
24
+ ### api-status
25
+
26
+ An API integration widget demonstrating:
27
+ - Fetching data from external APIs
28
+ - Error handling and retry logic
29
+ - Loading states and timeout handling
30
+ - **Environment variable configuration** (`${ENV_VAR:-default}`)
31
+ - **Config versioning** (`__version: "1.1.0"`)
32
+ - Configurable refresh intervals
33
+ - Request statistics tracking
34
+
35
+ ### system-metrics-chart
36
+
37
+ A data visualization widget demonstrating:
38
+ - **blessed-contrib line charts** for time-series data
39
+ - Multiple metric support (CPU, memory, network)
40
+ - Dynamic data updates with configurable refresh
41
+ - History management for rolling data windows
42
+ - Theme integration for chart styling
43
+
44
+ ## Installation
45
+
46
+ To install an example plugin:
47
+
48
+ ```bash
49
+ # Copy the plugin to the plugins directory
50
+ cp -r hello-world ~/.openclaw/plugins/
51
+ # or
52
+ cp -r weather-widget ~/.openclaw/plugins/
53
+ # or
54
+ cp -r api-status ~/.openclaw/plugins/
55
+ # or
56
+ cp -r system-metrics-chart ~/.openclaw/plugins/
57
+
58
+ # Restart clawdash
59
+ clawdash
60
+ ```
61
+
62
+ ## Example Comparison
63
+
64
+ | Example | Complexity | Demonstrates |
65
+ |---------|------------|--------------|
66
+ | `hello-world` | Beginner | Basic structure, lifecycle hooks |
67
+ | `weather-widget` | Beginner | Configuration, data fetching |
68
+ | `api-status` | Intermediate | API integration, error handling |
69
+ | `system-metrics-chart` | Advanced | Data visualization, charts, history |
70
+
71
+ ## Creating Your Own Plugin
72
+
73
+ 1. Copy the example that best matches your use case:
74
+ - Start with `hello-world` for simple widgets
75
+ - Use `weather-widget` for data-driven widgets
76
+ - Use `api-status` for external API integration
77
+ - Use `system-metrics-chart` for data visualization
78
+
79
+ 2. Update `plugin.json` with your plugin details
80
+ 3. Modify `index.js` to implement your widget logic
81
+ 4. Install to `~/.openclaw/plugins/`
82
+
83
+ ## Environment Variable Configuration
84
+
85
+ Widget configs support environment variable interpolation using the syntax:
86
+
87
+ ```json
88
+ {
89
+ "config": {
90
+ "__version": "1.0.0",
91
+ "apiUrl": "${API_URL:-https://default.example.com}",
92
+ "apiKey": "${API_KEY}",
93
+ "timeout": "${TIMEOUT:-5000}"
94
+ }
95
+ }
96
+ ```
97
+
98
+ - `${VAR}` - Uses the environment variable value, or keeps the literal if not set
99
+ - `${VAR:-default}` - Uses the environment variable, or the default value if not set
100
+
101
+ Set environment variables before running clawdash:
102
+
103
+ ```bash
104
+ export API_URL="https://api.example.com"
105
+ export API_KEY="your-secret-key"
106
+ clawdash
107
+ ```
108
+
109
+ ## Config Versioning
110
+
111
+ Widget configs can include a `__version` field to enable automatic migration when the dashboard is updated:
112
+
113
+ ```json
114
+ {
115
+ "config": {
116
+ "__version": "1.0.0",
117
+ "setting": "value"
118
+ }
119
+ }
120
+ ```
121
+
122
+ See the full documentation in `docs/PLUGINS.md`.
@@ -0,0 +1,294 @@
1
+ /**
2
+ * API Status Widget Plugin
3
+ * Demonstrates API integration patterns including:
4
+ * - Fetching data from external APIs
5
+ * - Error handling and retry logic
6
+ * - Loading states
7
+ * - Configurable refresh intervals
8
+ * - Timeout handling
9
+ */
10
+
11
+ import { BaseWidget } from '../../../src/widgets/plugin-api.js';
12
+
13
+ /**
14
+ * API Status Widget - Fetches and displays data from an external API
15
+ */
16
+ export default class ApiStatusWidget extends BaseWidget {
17
+ constructor(options = {}) {
18
+ super(options);
19
+ this.name = options.name || 'API Status';
20
+ this.description = 'Fetches data from external API';
21
+
22
+ // Internal state
23
+ this.loading = false;
24
+ this.error = null;
25
+ this.lastFetch = null;
26
+ this.refreshTimer = null;
27
+ this.data = null;
28
+ }
29
+
30
+ /**
31
+ * Initialize the widget
32
+ */
33
+ async init() {
34
+ this.log('info', 'API Status widget initialized');
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * Create the widget UI
40
+ * @param {Object} screen - Blessed screen object
41
+ * @param {Object} theme - Theme colors
42
+ */
43
+ async create(screen, theme = {}) {
44
+ const C = theme.colors || {};
45
+ const blessed = await import('blessed');
46
+
47
+ this.screen = screen;
48
+ this.theme = theme;
49
+
50
+ // Main container
51
+ this.box = blessed.default.box({
52
+ parent: screen,
53
+ width: '50%',
54
+ height: 7,
55
+ border: { type: 'line' },
56
+ label: ' API STATUS ',
57
+ style: { border: { fg: C.cyan || 'cyan' } },
58
+ });
59
+
60
+ // Status line (shows loading/success/error)
61
+ this.statusText = blessed.default.text({
62
+ parent: this.box,
63
+ top: 0,
64
+ left: 1,
65
+ content: 'Initializing...',
66
+ style: { fg: C.gray || 'gray' },
67
+ });
68
+
69
+ // Data content line
70
+ this.contentText = blessed.default.text({
71
+ parent: this.box,
72
+ top: 1,
73
+ left: 1,
74
+ content: '',
75
+ style: { fg: C.white || 'white' },
76
+ wrap: true,
77
+ });
78
+
79
+ // Last updated line
80
+ this.updatedText = blessed.default.text({
81
+ parent: this.box,
82
+ top: 3,
83
+ left: 1,
84
+ content: 'Never updated',
85
+ style: { fg: C.gray || 'gray' },
86
+ });
87
+
88
+ // Stats line (shows request count, errors)
89
+ this.statsText = blessed.default.text({
90
+ parent: this.box,
91
+ top: 4,
92
+ left: 1,
93
+ content: 'Requests: 0 | Errors: 0',
94
+ style: { fg: C.gray || 'gray' },
95
+ });
96
+
97
+ this.loaded = true;
98
+ this.log('debug', 'API Status widget UI created');
99
+
100
+ // Start auto-refresh if configured
101
+ const refreshInterval = this.config.refreshInterval || 60000;
102
+ if (refreshInterval > 0) {
103
+ this.startAutoRefresh(refreshInterval);
104
+ }
105
+
106
+ return this;
107
+ }
108
+
109
+ /**
110
+ * Fetch data from the configured API
111
+ * Includes retry logic and timeout handling
112
+ */
113
+ async getData() {
114
+ const apiUrl = this.config.apiUrl || 'https://api.github.com/zen';
115
+ const timeout = this.config.timeout || 5000;
116
+ const maxRetries = this.config.retries || 3;
117
+
118
+ let lastError = null;
119
+
120
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
121
+ try {
122
+ this.loading = true;
123
+ this.error = null;
124
+ this.updateStatus('loading', `Fetching (attempt ${attempt}/${maxRetries})...`);
125
+
126
+ const controller = new AbortController();
127
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
128
+
129
+ const response = await fetch(apiUrl, {
130
+ signal: controller.signal,
131
+ headers: {
132
+ 'User-Agent': 'Claw-Dashboard-Widget/1.0',
133
+ },
134
+ });
135
+
136
+ clearTimeout(timeoutId);
137
+
138
+ if (!response.ok) {
139
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
140
+ }
141
+
142
+ // Handle different response types
143
+ const contentType = response.headers.get('content-type') || '';
144
+ let data;
145
+
146
+ if (contentType.includes('application/json')) {
147
+ data = await response.json();
148
+ } else {
149
+ data = await response.text();
150
+ }
151
+
152
+ this.loading = false;
153
+ this.lastFetch = new Date();
154
+
155
+ return {
156
+ success: true,
157
+ data,
158
+ timestamp: this.lastFetch.toISOString(),
159
+ apiUrl,
160
+ };
161
+ } catch (err) {
162
+ lastError = err;
163
+ this.log('warn', `API fetch attempt ${attempt} failed: ${err.message}`);
164
+
165
+ // Don't retry on abort (timeout)
166
+ if (err.name === 'AbortError') {
167
+ break;
168
+ }
169
+
170
+ // Wait before retrying (exponential backoff)
171
+ if (attempt < maxRetries) {
172
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
173
+ }
174
+ }
175
+ }
176
+
177
+ // All retries failed
178
+ this.loading = false;
179
+ this.error = lastError;
180
+
181
+ return {
182
+ success: false,
183
+ error: lastError?.message || 'Unknown error',
184
+ timestamp: new Date().toISOString(),
185
+ apiUrl,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Render the widget with fetched data
191
+ * @param {Object} result - Result from getData()
192
+ */
193
+ render(result) {
194
+ if (!this.box) return;
195
+
196
+ if (result.success) {
197
+ this.updateStatus('success', 'Connected');
198
+
199
+ // Format the data for display
200
+ let content;
201
+ if (typeof result.data === 'object') {
202
+ content = JSON.stringify(result.data, null, 0).slice(0, 100);
203
+ } else {
204
+ content = String(result.data).slice(0, 100);
205
+ }
206
+
207
+ this.contentText.setContent(content);
208
+ this.contentText.style.fg = this.theme?.colors?.white || 'white';
209
+ } else {
210
+ this.updateStatus('error', `Error: ${result.error}`);
211
+ this.contentText.setContent('Unable to fetch data');
212
+ this.contentText.style.fg = this.theme?.colors?.red || 'red';
213
+ }
214
+
215
+ // Update timestamp
216
+ this.updatedText.setContent(`Last: ${result.timestamp || 'Never'}`);
217
+
218
+ // Update stats
219
+ const stats = this.getStats();
220
+ this.statsText.setContent(`Requests: ${stats.requests} | Errors: ${stats.errors}`);
221
+ }
222
+
223
+ /**
224
+ * Update the status indicator
225
+ * @private
226
+ */
227
+ updateStatus(status, message) {
228
+ const colors = {
229
+ loading: this.theme?.colors?.yellow || 'yellow',
230
+ success: this.theme?.colors?.green || 'green',
231
+ error: this.theme?.colors?.red || 'red',
232
+ };
233
+
234
+ this.statusText.setContent(message);
235
+ this.statusText.style.fg = colors[status] || 'white';
236
+ }
237
+
238
+ /**
239
+ * Track request statistics
240
+ * @private
241
+ */
242
+ getStats() {
243
+ if (!this._stats) {
244
+ this._stats = { requests: 0, errors: 0 };
245
+ }
246
+ return this._stats;
247
+ }
248
+
249
+ /**
250
+ * Start auto-refresh timer
251
+ * @param {number} intervalMs - Refresh interval in milliseconds
252
+ */
253
+ startAutoRefresh(intervalMs) {
254
+ this.stopAutoRefresh();
255
+ this.refreshTimer = setInterval(() => {
256
+ if (!this.loading) {
257
+ this.getData()
258
+ .then(data => this.render(data))
259
+ .catch(err => this.log('error', `Auto-refresh failed: ${err.message}`));
260
+ }
261
+ }, intervalMs);
262
+
263
+ this.log('debug', `Auto-refresh started (${intervalMs}ms interval)`);
264
+ }
265
+
266
+ /**
267
+ * Stop auto-refresh timer
268
+ */
269
+ stopAutoRefresh() {
270
+ if (this.refreshTimer) {
271
+ clearInterval(this.refreshTimer);
272
+ this.refreshTimer = null;
273
+ this.log('debug', 'Auto-refresh stopped');
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Destroy the widget
279
+ */
280
+ async destroy() {
281
+ this.stopAutoRefresh();
282
+
283
+ if (this.box) {
284
+ this.box.destroy();
285
+ this.box = null;
286
+ }
287
+
288
+ this.loaded = false;
289
+ this.log('info', 'API Status widget destroyed');
290
+ }
291
+ }
292
+
293
+ // Export named export for flexibility
294
+ export { ApiStatusWidget };
@@ -0,0 +1,19 @@
1
+ {
2
+ "id": "example-api-status",
3
+ "name": "API Status",
4
+ "description": "Example widget demonstrating API integration with error handling, loading states, and environment variable configuration",
5
+ "version": "1.1.0",
6
+ "author": "Claw Dashboard Team",
7
+ "category": "example",
8
+ "type": "widget",
9
+ "lazyLoad": true,
10
+ "priority": 100,
11
+ "config": {
12
+ "__version": "1.1.0",
13
+ "apiUrl": "${API_STATUS_URL:-https://api.github.com/zen}",
14
+ "apiKey": "${API_STATUS_KEY:-}",
15
+ "refreshInterval": "${API_STATUS_REFRESH:-60000}",
16
+ "timeout": "${API_STATUS_TIMEOUT:-5000}",
17
+ "retries": "${API_STATUS_RETRIES:-3}"
18
+ }
19
+ }