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
@@ -0,0 +1,230 @@
1
+ /**
2
+ * CLI Import Snapshot Module
3
+ * Imports dashboard configuration from a snapshot file
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import os from 'os';
8
+ import { join, resolve } from 'path';
9
+ import {
10
+ importSnapshotFromFile,
11
+ validateSnapshot,
12
+ mergeSnapshotSettings,
13
+ getSnapshotSummary,
14
+ getSnapshotsDirectory,
15
+ listSnapshots,
16
+ } from '../snapshot.js';
17
+ import { DEFAULT_SETTINGS, PATHS } from '../config.js';
18
+
19
+ /**
20
+ * Save settings to file
21
+ * @param {Object} settings - Settings to save
22
+ */
23
+ function saveSettings(settings) {
24
+ try {
25
+ const settingsPath = PATHS.SETTINGS;
26
+ const dir = PATHS.OPENCLAW_DIR;
27
+ if (!fs.existsSync(dir)) {
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ }
30
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
31
+ } catch (err) {
32
+ throw new Error(`Failed to save settings: ${err.message}`);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Run the import-snapshot CLI command
38
+ * @param {string[]} args - CLI arguments
39
+ * @returns {number} Exit code
40
+ */
41
+ export async function runImportSnapshotCli(args) {
42
+ const jsonOutput = args.includes('--json') || args.includes('-j');
43
+ const dryRun = args.includes('--dry-run') || args.includes('-d');
44
+ const force = args.includes('--force') || args.includes('-f');
45
+ const showHelp = args.includes('--help') || args.includes('-h');
46
+ const listMode = args.includes('--list') || args.includes('-l');
47
+
48
+ // Get file path (first non-flag argument)
49
+ const filePath = args.find(a => !a.startsWith('-'));
50
+
51
+ if (showHelp) {
52
+ console.log(`
53
+ Import Dashboard Snapshot for Claw Dashboard
54
+
55
+ Usage: clawdash import-snapshot [path] [options]
56
+
57
+ Arguments:
58
+ path Path to snapshot file (optional with --list)
59
+
60
+ Options:
61
+ -l, --list List available snapshots
62
+ -d, --dry-run Validate without applying
63
+ -f, --force Skip confirmation
64
+ -j, --json Output results as JSON
65
+ -h, --help Show this help message
66
+
67
+ Examples:
68
+ clawdash import-snapshot --list
69
+ clawdash import-snapshot ~/my-layout.json
70
+ clawdash import-snapshot ~/.openclaw/snapshots/claw-snapshot-*.json --dry-run
71
+ clawdash import-snapshot ~/backup.json --force
72
+ `);
73
+ return 0;
74
+ }
75
+
76
+ // List mode
77
+ if (listMode) {
78
+ const snapshots = listSnapshots();
79
+ if (jsonOutput) {
80
+ console.log(JSON.stringify({ snapshots }, null, 2));
81
+ } else {
82
+ if (snapshots.length === 0) {
83
+ console.log('No snapshots found in ~/.openclaw/snapshots/');
84
+ } else {
85
+ console.log('Available snapshots:');
86
+ console.log('');
87
+ snapshots.forEach((s, i) => {
88
+ const date = new Date(s.createdAt).toLocaleDateString();
89
+ console.log(` ${i + 1}. ${s.name}`);
90
+ console.log(` Created: ${date}`);
91
+ console.log(` Path: ${s.path}`);
92
+ if (s.metadata) {
93
+ console.log(` Widgets: ${s.metadata.widgetCount}, Plugins: ${s.metadata.pluginCount}`);
94
+ }
95
+ console.log('');
96
+ });
97
+ }
98
+ }
99
+ return 0;
100
+ }
101
+
102
+ // Require file path for import
103
+ if (!filePath) {
104
+ if (jsonOutput) {
105
+ console.log(JSON.stringify({
106
+ success: false,
107
+ error: 'File path is required (use --list to see available snapshots)',
108
+ }, null, 2));
109
+ } else {
110
+ console.error('Error: File path is required');
111
+ console.error('Run with --list to see available snapshots');
112
+ console.error('Run with --help for usage information');
113
+ }
114
+ return 1;
115
+ }
116
+
117
+ try {
118
+ // Resolve the path
119
+ let resolvedPath = filePath;
120
+ if (filePath.startsWith('~')) {
121
+ resolvedPath = join(os.homedir(), filePath.slice(1));
122
+ }
123
+ resolvedPath = resolve(resolvedPath);
124
+
125
+ // Import and validate
126
+ const result = importSnapshotFromFile(resolvedPath);
127
+
128
+ if (!result.success) {
129
+ if (jsonOutput) {
130
+ console.log(JSON.stringify({
131
+ success: false,
132
+ error: result.error,
133
+ path: resolvedPath,
134
+ }, null, 2));
135
+ } else {
136
+ console.error(`✗ Import failed: ${result.error}`);
137
+ }
138
+ return 1;
139
+ }
140
+
141
+ const { snapshot } = result;
142
+
143
+ // Dry run mode - just validate and show info
144
+ if (dryRun) {
145
+ const summary = getSnapshotSummary(snapshot);
146
+ if (jsonOutput) {
147
+ console.log(JSON.stringify({
148
+ success: true,
149
+ dryRun: true,
150
+ path: resolvedPath,
151
+ snapshot: {
152
+ name: snapshot.name,
153
+ description: snapshot.description,
154
+ version: snapshot.dashboardVersion,
155
+ schemaVersion: snapshot.schemaVersion,
156
+ createdAt: snapshot.createdAt,
157
+ metadata: snapshot.metadata,
158
+ },
159
+ summary: summary.split('\n'),
160
+ }, null, 2));
161
+ } else {
162
+ console.log('✓ Snapshot is valid (dry run, no changes applied)');
163
+ console.log('');
164
+ console.log(summary);
165
+ }
166
+ return 0;
167
+ }
168
+
169
+ // Show summary before applying (unless --force)
170
+ if (!jsonOutput && !force) {
171
+ const summary = getSnapshotSummary(snapshot);
172
+ console.log('Snapshot to import:');
173
+ console.log('');
174
+ console.log(summary);
175
+ console.log('');
176
+ console.log('Run with --force to apply, or --dry-run to preview');
177
+ return 0;
178
+ }
179
+
180
+ // Load current settings and merge
181
+ const currentSettings = { ...DEFAULT_SETTINGS };
182
+ try {
183
+ if (fs.existsSync(PATHS.SETTINGS)) {
184
+ const data = fs.readFileSync(PATHS.SETTINGS, 'utf8');
185
+ Object.assign(currentSettings, JSON.parse(data));
186
+ }
187
+ } catch (err) {
188
+ // Continue with defaults
189
+ }
190
+
191
+ const mergedSettings = mergeSnapshotSettings(currentSettings, snapshot.settings);
192
+
193
+ // Save merged settings
194
+ saveSettings(mergedSettings);
195
+
196
+ if (jsonOutput) {
197
+ console.log(JSON.stringify({
198
+ success: true,
199
+ path: resolvedPath,
200
+ snapshot: {
201
+ name: snapshot.name,
202
+ version: snapshot.dashboardVersion,
203
+ createdAt: snapshot.createdAt,
204
+ },
205
+ applied: Object.keys(snapshot.settings),
206
+ }, null, 2));
207
+ } else {
208
+ console.log('✓ Snapshot imported successfully');
209
+ console.log(` Name: ${snapshot.name}`);
210
+ console.log(` Version: ${snapshot.dashboardVersion}`);
211
+ console.log('');
212
+ console.log('Settings have been merged and saved.');
213
+ console.log('Restart Claw Dashboard to apply all changes.');
214
+ }
215
+
216
+ return 0;
217
+ } catch (err) {
218
+ if (jsonOutput) {
219
+ console.log(JSON.stringify({
220
+ success: false,
221
+ error: err.message,
222
+ }, null, 2));
223
+ } else {
224
+ console.error(`✗ Import error: ${err.message}`);
225
+ }
226
+ return 1;
227
+ }
228
+ }
229
+
230
+ export default { runImportSnapshotCli };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CLI Module Index
3
+ * Centralized export for all CLI command handlers
4
+ */
5
+
6
+ export { parseCliArgs } from './args.js';
7
+ export { showHelp } from './help.js';
8
+ export { showVersion } from './version.js';
9
+ export { runValidatePluginCli } from './validate-plugin.js';
10
+ export { runValidateConfigCli } from './validate-config.js';
11
+ export { runExportSnapshotCli } from './export-snapshot.js';
12
+ export { runImportSnapshotCli } from './import-snapshot.js';
13
+ export { runListTemplatesCli } from './list-templates.js';
14
+ export { runExportScheduleCli } from './export-schedule.js';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * CLI List Templates Module
3
+ * Lists available widget templates for scaffolding
4
+ */
5
+
6
+ import { listTemplates } from '../plugin-scaffold.js';
7
+
8
+ /**
9
+ * Run the list-templates CLI command
10
+ * @param {string[]} args - CLI arguments
11
+ * @returns {number} Exit code
12
+ */
13
+ export async function runListTemplatesCli(args) {
14
+ const showHelp = args.includes('--help') || args.includes('-h');
15
+
16
+ if (showHelp) {
17
+ console.log(`
18
+ List Available Widget Templates
19
+
20
+ Usage: clawdash list-templates [options]
21
+
22
+ Options:
23
+ -j, --json Output as JSON
24
+ -h, --help Show this help message
25
+
26
+ Examples:
27
+ clawdash list-templates
28
+ clawdash list-templates --json
29
+ `);
30
+ return 0;
31
+ }
32
+
33
+ const jsonOutput = args.includes('--json') || args.includes('-j');
34
+
35
+ try {
36
+ const templates = listTemplates();
37
+
38
+ if (jsonOutput) {
39
+ console.log(JSON.stringify(templates, null, 2));
40
+ } else {
41
+ console.log('');
42
+ console.log('╔══════════════════════════════════════════════════════════════╗');
43
+ console.log('║ Available Widget Templates ║');
44
+ console.log('╚══════════════════════════════════════════════════════════════╝');
45
+ console.log('');
46
+
47
+ templates.forEach((template, index) => {
48
+ console.log(` ${index + 1}. ${template.name}`);
49
+ console.log(` ID: ${template.id}`);
50
+ console.log(` ${template.description}`);
51
+ console.log('');
52
+ });
53
+
54
+ console.log('Usage:');
55
+ console.log(' clawdash create-plugin <name> --template <id>');
56
+ console.log('');
57
+ console.log('Example:');
58
+ console.log(' clawdash create-plugin my-widget --template api');
59
+ console.log('');
60
+ }
61
+
62
+ return 0;
63
+ } catch (err) {
64
+ console.error(`Error: ${err.message}`);
65
+ return 1;
66
+ }
67
+ }
68
+
69
+ export default { runListTemplatesCli };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * CLI Validate Config Module
3
+ * Validates dashboard configuration files
4
+ */
5
+
6
+ import os from 'os';
7
+ import { resolve, join } from 'path';
8
+ import { validateConfigFile, formatConfigValidationResult, getDefaultConfigPath } from '../config-validator.js';
9
+
10
+ /**
11
+ * Run the validate-config CLI command
12
+ * @param {string[]} args - CLI arguments
13
+ * @returns {number} Exit code
14
+ */
15
+ export async function runValidateConfigCli(args) {
16
+ const configPath = args[0];
17
+ const jsonOutput = args.includes('--json') || args.includes('-j');
18
+ const showHelp = args.includes('--help') || args.includes('-h');
19
+ const strict = args.includes('--strict') || args.includes('-s');
20
+
21
+ if (showHelp) {
22
+ console.log(`
23
+ Validate Dashboard Configuration for Claw Dashboard
24
+
25
+ Usage: clawdash validate-config [path] [options]
26
+
27
+ Arguments:
28
+ path Path to configuration file (optional)
29
+ Defaults to: ~/.openclaw/dashboard-settings.json
30
+
31
+ Options:
32
+ -j, --json Output results as JSON
33
+ -s, --strict Fail on unknown properties
34
+ -h, --help Show this help message
35
+
36
+ Examples:
37
+ clawdash validate-config
38
+ clawdash validate-config ~/.openclaw/dashboard-settings.json
39
+ clawdash validate-config ./my-config.json --json
40
+ clawdash validate-config --strict
41
+ `);
42
+ return 0;
43
+ }
44
+
45
+ // Determine the config file path
46
+ const targetPath = configPath || getDefaultConfigPath();
47
+
48
+ // Resolve the path (handle ~ expansion)
49
+ let resolvedPath = targetPath;
50
+ if (targetPath.startsWith('~')) {
51
+ resolvedPath = join(os.homedir(), targetPath.slice(1));
52
+ }
53
+ resolvedPath = resolve(resolvedPath);
54
+
55
+ // Validate the configuration
56
+ const result = validateConfigFile(resolvedPath, { strict });
57
+
58
+ // Output results
59
+ if (jsonOutput) {
60
+ const output = {
61
+ valid: result.valid,
62
+ path: resolvedPath,
63
+ errors: result.errors,
64
+ warnings: result.warnings,
65
+ info: result.info,
66
+ stats: result.stats,
67
+ };
68
+ console.log(JSON.stringify(output, null, 2));
69
+ } else {
70
+ console.log(formatConfigValidationResult(result, resolvedPath));
71
+ }
72
+
73
+ return result.valid ? 0 : 1;
74
+ }
75
+
76
+ export default { runValidateConfigCli };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * CLI Validate Plugin Module
3
+ * Validates plugin.json manifest files
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import os from 'os';
8
+ import { dirname, join, resolve } from 'path';
9
+ import { validateManifest, validatePluginIdFormat } from '../plugin-manifest-validator.js';
10
+
11
+ /**
12
+ * Run the validate-plugin CLI command
13
+ * @param {string[]} args - CLI arguments
14
+ * @returns {number} Exit code
15
+ */
16
+ export async function runValidatePluginCli(args) {
17
+ const pluginPath = args[0];
18
+ const jsonOutput = args.includes('--json') || args.includes('-j');
19
+ const verbose = args.includes('--verbose') || args.includes('-v');
20
+ const showHelp = args.includes('--help') || args.includes('-h');
21
+
22
+ if (showHelp) {
23
+ console.log(`
24
+ Validate Plugin Manifest for Claw Dashboard
25
+
26
+ Usage: clawdash validate-plugin <path> [options]
27
+
28
+ Arguments:
29
+ path Path to plugin.json file or plugin directory
30
+
31
+ Options:
32
+ -j, --json Output results as JSON
33
+ -v, --verbose Show detailed output including code analysis
34
+ -h, --help Show this help message
35
+
36
+ Examples:
37
+ clawdash validate-plugin ./my-widget/plugin.json
38
+ clawdash validate-plugin ~/.openclaw/plugins/my-widget
39
+ clawdash validate-plugin ./my-widget --json
40
+ clawdash validate-plugin ./my-widget --verbose
41
+ `);
42
+ return 0;
43
+ }
44
+
45
+ if (!pluginPath) {
46
+ console.error('Error: Path is required');
47
+ console.error('Run with --help for usage information');
48
+ return 1;
49
+ }
50
+
51
+ // Resolve the path
52
+ let resolvedPath = pluginPath;
53
+ if (pluginPath.startsWith('~')) {
54
+ resolvedPath = join(os.homedir(), pluginPath.slice(1));
55
+ }
56
+ resolvedPath = resolve(resolvedPath);
57
+
58
+ // Check if path exists
59
+ if (!fs.existsSync(resolvedPath)) {
60
+ const result = {
61
+ valid: false,
62
+ error: `Path does not exist: ${pluginPath}`,
63
+ };
64
+ if (jsonOutput) {
65
+ console.log(JSON.stringify(result, null, 2));
66
+ } else {
67
+ console.error(`Error: ${result.error}`);
68
+ }
69
+ return 1;
70
+ }
71
+
72
+ // Determine the manifest file path
73
+ let manifestPath = resolvedPath;
74
+ const stats = fs.statSync(resolvedPath);
75
+ if (stats.isDirectory()) {
76
+ manifestPath = join(resolvedPath, 'plugin.json');
77
+ if (!fs.existsSync(manifestPath)) {
78
+ const result = {
79
+ valid: false,
80
+ path: resolvedPath,
81
+ error: `No plugin.json found in directory: ${pluginPath}`,
82
+ };
83
+ if (jsonOutput) {
84
+ console.log(JSON.stringify(result, null, 2));
85
+ } else {
86
+ console.error(`Error: ${result.error}`);
87
+ }
88
+ return 1;
89
+ }
90
+ }
91
+
92
+ // Read and parse the manifest
93
+ let manifest;
94
+ try {
95
+ const content = fs.readFileSync(manifestPath, 'utf8');
96
+ manifest = JSON.parse(content);
97
+ } catch (err) {
98
+ const result = {
99
+ valid: false,
100
+ path: manifestPath,
101
+ error: `Failed to read/parse plugin.json: ${err.message}`,
102
+ };
103
+ if (jsonOutput) {
104
+ console.log(JSON.stringify(result, null, 2));
105
+ } else {
106
+ console.error(`Error: ${result.error}`);
107
+ }
108
+ return 1;
109
+ }
110
+
111
+ // Validate the manifest
112
+ const validation = validateManifest(manifest);
113
+
114
+ // Also validate the plugin ID if present
115
+ let idValidation = { valid: true };
116
+ if (manifest.id) {
117
+ idValidation = validatePluginIdFormat(manifest.id);
118
+ }
119
+
120
+ const result = {
121
+ valid: validation.valid && idValidation.valid,
122
+ path: manifestPath,
123
+ errors: validation.errors,
124
+ id: manifest.id || null,
125
+ name: manifest.name || null,
126
+ version: manifest.version || null,
127
+ };
128
+
129
+ if (!idValidation.valid) {
130
+ result.errors.push(`Invalid plugin ID: ${idValidation.error}`);
131
+ }
132
+
133
+ // Enhanced validation for verbose mode
134
+ let warnings = [];
135
+ if (verbose && result.valid) {
136
+ // Check for recommended fields
137
+ if (!manifest.description || manifest.description === 'A custom widget plugin for Claw Dashboard') {
138
+ warnings.push('Add a meaningful description to your plugin');
139
+ }
140
+ if (!manifest.author) {
141
+ warnings.push('Missing author - recommended for plugin distribution');
142
+ }
143
+ if (!manifest.config || Object.keys(manifest.config).length === 0) {
144
+ warnings.push('Consider adding configurable options to your plugin');
145
+ }
146
+ // Check for index.js if it's a widget type
147
+ if (manifest.type === 'widget') {
148
+ const indexPath = stats.isDirectory() ? join(resolvedPath, 'index.js') : join(dirname(resolvedPath), 'index.js');
149
+ if (!fs.existsSync(indexPath)) {
150
+ result.valid = false;
151
+ result.errors.push('Widget plugins must have an index.js file');
152
+ }
153
+ }
154
+ }
155
+
156
+ // Output results
157
+ if (jsonOutput) {
158
+ if (verbose) {
159
+ result.warnings = warnings;
160
+ }
161
+ console.log(JSON.stringify(result, null, 2));
162
+ } else {
163
+ if (result.valid) {
164
+ console.log(`✓ Valid plugin manifest: ${manifestPath}`);
165
+ console.log(` ID: ${result.id}`);
166
+ console.log(` Name: ${result.name}`);
167
+ console.log(` Version: ${result.version}`);
168
+ if (verbose && warnings.length > 0) {
169
+ console.log('');
170
+ console.log('Warnings:');
171
+ warnings.forEach(warning => {
172
+ console.log(` ⚠ ${warning}`);
173
+ });
174
+ }
175
+ } else {
176
+ console.error(`✗ Invalid plugin manifest: ${manifestPath}`);
177
+ console.error(' Errors:');
178
+ result.errors.forEach(error => {
179
+ console.error(` - ${error}`);
180
+ });
181
+ }
182
+ }
183
+
184
+ return result.valid ? 0 : 1;
185
+ }
186
+
187
+ export default { runValidatePluginCli };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * CLI Version Module
3
+ * Displays version information for Claw Dashboard
4
+ */
5
+
6
+ import { DASHBOARD_VERSION } from '../config.js';
7
+
8
+ /**
9
+ * Display version information
10
+ */
11
+ export function showVersion() {
12
+ console.log(`clawdash ${DASHBOARD_VERSION}`);
13
+ }
14
+
15
+ export default { showVersion };