clawsql 0.2.1 → 0.2.3

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 (49) hide show
  1. package/README.md +25 -5
  2. package/dist/bin/clawsql.js +0 -0
  3. package/dist/cli/agent/handler.d.ts +13 -31
  4. package/dist/cli/agent/handler.d.ts.map +1 -1
  5. package/dist/cli/agent/handler.js +111 -149
  6. package/dist/cli/agent/handler.js.map +1 -1
  7. package/dist/cli/agent/index.d.ts +2 -1
  8. package/dist/cli/agent/index.d.ts.map +1 -1
  9. package/dist/cli/agent/index.js +7 -1
  10. package/dist/cli/agent/index.js.map +1 -1
  11. package/dist/cli/agent/openclaw-integration.d.ts +92 -25
  12. package/dist/cli/agent/openclaw-integration.d.ts.map +1 -1
  13. package/dist/cli/agent/openclaw-integration.js +312 -195
  14. package/dist/cli/agent/openclaw-integration.js.map +1 -1
  15. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  16. package/dist/cli/commands/cleanup.js +26 -15
  17. package/dist/cli/commands/cleanup.js.map +1 -1
  18. package/dist/cli/commands/doctor.d.ts +0 -4
  19. package/dist/cli/commands/doctor.d.ts.map +1 -1
  20. package/dist/cli/commands/doctor.js +232 -581
  21. package/dist/cli/commands/doctor.js.map +1 -1
  22. package/dist/cli/commands/openclaw.d.ts +9 -0
  23. package/dist/cli/commands/openclaw.d.ts.map +1 -0
  24. package/dist/cli/commands/openclaw.js +236 -0
  25. package/dist/cli/commands/openclaw.js.map +1 -0
  26. package/dist/cli/commands/start.d.ts.map +1 -1
  27. package/dist/cli/commands/start.js +264 -8
  28. package/dist/cli/commands/start.js.map +1 -1
  29. package/dist/cli/commands/status.d.ts.map +1 -1
  30. package/dist/cli/commands/status.js +7 -34
  31. package/dist/cli/commands/status.js.map +1 -1
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +2 -0
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/cli/repl.js +1 -1
  36. package/dist/cli/repl.js.map +1 -1
  37. package/dist/cli/utils/ai-config.d.ts +32 -0
  38. package/dist/cli/utils/ai-config.d.ts.map +1 -0
  39. package/dist/cli/utils/ai-config.js +78 -0
  40. package/dist/cli/utils/ai-config.js.map +1 -0
  41. package/dist/cli/utils/docker-files.d.ts.map +1 -1
  42. package/dist/cli/utils/docker-files.js +2 -0
  43. package/dist/cli/utils/docker-files.js.map +1 -1
  44. package/docker/openclaw/entrypoint.sh +102 -0
  45. package/docker/openclaw/openclaw.json +4 -1
  46. package/docker/orchestrator/orchestrator-schema.sql +837 -0
  47. package/docker-compose.yml +22 -14
  48. package/init/metadata.sql +14 -2
  49. package/package.json +1 -1
@@ -3,7 +3,6 @@
3
3
  * ClawSQL CLI - Doctor Command
4
4
  *
5
5
  * Diagnoses system health and suggests fixes for common issues.
6
- * Similar to `brew doctor` or `npm doctor`.
7
6
  */
8
7
  Object.defineProperty(exports, "__esModule", { value: true });
9
8
  exports.doctorCommand = void 0;
@@ -11,189 +10,128 @@ const components_js_1 = require("../ui/components.js");
11
10
  const child_process_1 = require("child_process");
12
11
  const openclaw_integration_js_1 = require("../agent/openclaw-integration.js");
13
12
  const docker_prereq_js_1 = require("../utils/docker-prereq.js");
14
- /**
15
- * Doctor command
16
- */
13
+ const ai_config_js_1 = require("../utils/ai-config.js");
14
+ // ============================================================================
15
+ // Command Definition
16
+ // ============================================================================
17
17
  exports.doctorCommand = {
18
18
  name: 'doctor',
19
19
  description: 'Diagnose system issues and suggest fixes',
20
20
  usage: '/doctor [--fix]',
21
- handler: async (args, ctx) => {
22
- const formatter = ctx.formatter;
23
- const shouldFix = args.includes('--fix');
24
- console.log(formatter.header('ClawSQL Doctor'));
21
+ handler: async (_args, ctx) => {
22
+ console.log(ctx.formatter.header('ClawSQL Doctor'));
25
23
  console.log(components_js_1.theme.muted('Running diagnostics...\n'));
26
24
  const results = [];
27
- // Run all diagnostic checks
28
25
  await runDiagnostics(ctx, results);
29
- // Display results
30
26
  displayResults(results);
31
- // Summary
32
- const errors = results.filter(r => r.severity === 'error');
33
- const warnings = results.filter(r => r.severity === 'warning');
34
- console.log();
35
- if (errors.length === 0 && warnings.length === 0) {
36
- console.log(components_js_1.theme.success(`${components_js_1.indicators.check} All systems healthy!`));
37
- }
38
- else {
39
- console.log(components_js_1.theme.warning(`Found ${errors.length} error(s) and ${warnings.length} warning(s)`));
40
- if (!shouldFix && (errors.length > 0 || warnings.length > 0)) {
41
- console.log(components_js_1.theme.muted('\nSome issues may have automatic fixes available.'));
42
- }
43
- }
27
+ printSummary(results);
44
28
  },
45
29
  };
46
- /**
47
- * Run all diagnostic checks
48
- */
30
+ // ============================================================================
31
+ // Diagnostic Runner
32
+ // ============================================================================
49
33
  async function runDiagnostics(ctx, results) {
50
- // Platform checks
51
34
  const runtime = await checkContainerRuntime(results);
52
- await checkDockerImages(runtime, results);
53
- await checkClawSQLAPI(ctx, results);
54
- await checkOrchestrator(ctx, results);
55
- await checkProxySQL(ctx, results);
56
- await checkPrometheus(ctx, results);
57
- await checkOpenClaw(ctx, results);
58
- // Configuration checks
35
+ await Promise.all([
36
+ checkDockerImages(runtime, results),
37
+ checkClawSQLAPI(ctx, results),
38
+ checkOrchestrator(ctx, results),
39
+ checkProxySQL(ctx, results),
40
+ checkPrometheus(ctx, results),
41
+ checkOpenClaw(ctx, results),
42
+ ]);
59
43
  checkConfiguration(ctx, results);
60
- // MySQL checks
61
- await checkMySQLInstances(ctx, results);
62
- await checkReplicationTopology(ctx, results);
63
- // Sync check
64
- await checkProxySQLSync(ctx, results);
65
44
  }
66
- /**
67
- * Check container runtime availability
68
- */
45
+ // ============================================================================
46
+ // Individual Checks
47
+ // ============================================================================
69
48
  async function checkContainerRuntime(results) {
70
- const runtimes = ['docker', 'podman'];
71
- let foundRuntime = '';
72
- for (const runtime of runtimes) {
73
- try {
74
- const result = await execCommand([runtime, 'info'], true);
75
- if (result.success) {
76
- foundRuntime = runtime;
77
- break;
78
- }
79
- }
80
- catch {
81
- // Continue to next runtime
82
- }
83
- }
84
- if (foundRuntime) {
85
- results.push({
86
- name: 'Container Runtime',
87
- severity: 'ok',
88
- message: `${foundRuntime} is installed and running`,
89
- });
90
- // Check if containers are running
91
- try {
92
- const psResult = await execCommand([foundRuntime, 'ps', '--filter', 'name=clawsql', '--filter', 'name=orchestrator',
93
- '--filter', 'name=proxysql', '-q'], true);
94
- const containerCount = psResult.stdout.trim().split('\n').filter(Boolean).length;
95
- if (containerCount === 0) {
96
- results.push({
97
- name: 'Platform Containers',
98
- severity: 'warning',
99
- message: 'No ClawSQL containers are running',
100
- fix: 'Start the platform with: /start',
101
- fixCommand: '/start',
102
- });
103
- }
104
- else {
105
- results.push({
106
- name: 'Platform Containers',
107
- severity: 'ok',
108
- message: `${containerCount} container(s) running`,
109
- });
110
- }
111
- }
112
- catch {
113
- results.push({
114
- name: 'Platform Containers',
115
- severity: 'warning',
116
- message: 'Could not check container status',
117
- });
118
- }
119
- }
120
- else {
49
+ const runtime = await (0, docker_prereq_js_1.detectRuntime)();
50
+ if (!runtime) {
121
51
  results.push({
122
52
  name: 'Container Runtime',
123
53
  severity: 'error',
124
54
  message: 'No container runtime found (docker or podman required)',
125
55
  fix: 'Install Docker from https://docs.docker.com/get-docker/',
126
56
  });
57
+ return null;
127
58
  }
128
- return foundRuntime || null;
129
- }
130
- /**
131
- * Check if Docker images are installed
132
- */
133
- async function checkDockerImages(runtime, results) {
134
- if (!runtime) {
135
- return; // Already reported in container runtime check
59
+ results.push({
60
+ name: 'Container Runtime',
61
+ severity: 'ok',
62
+ message: `${runtime} is installed and running`,
63
+ });
64
+ const psResult = await execCommand([runtime, 'ps', '--filter', 'name=clawsql', '--filter', 'name=orchestrator', '--filter', 'name=proxysql', '-q'], true);
65
+ const containerCount = psResult.stdout.trim().split('\n').filter(Boolean).length;
66
+ if (containerCount === 0) {
67
+ results.push({
68
+ name: 'Platform Containers',
69
+ severity: 'warning',
70
+ message: 'No ClawSQL containers are running',
71
+ fix: 'Start the platform with: /start',
72
+ fixCommand: '/start',
73
+ });
136
74
  }
137
- const imageStatus = await (0, docker_prereq_js_1.checkImagesInstalled)(runtime);
138
- if (imageStatus.installed.length === imageStatus.total) {
75
+ else {
139
76
  results.push({
140
- name: 'Docker Images',
77
+ name: 'Platform Containers',
141
78
  severity: 'ok',
142
- message: `All ${imageStatus.total} images installed`,
79
+ message: `${containerCount} container(s) running`,
143
80
  });
144
81
  }
145
- else if (imageStatus.installed.length === 0) {
82
+ return runtime;
83
+ }
84
+ async function checkDockerImages(runtime, results) {
85
+ if (!runtime)
86
+ return;
87
+ const status = await (0, docker_prereq_js_1.checkImagesInstalled)(runtime);
88
+ if (status.installed.length === status.total) {
89
+ results.push({ name: 'Docker Images', severity: 'ok', message: `All ${status.total} images installed` });
90
+ }
91
+ else if (status.installed.length === 0) {
146
92
  results.push({
147
93
  name: 'Docker Images',
148
94
  severity: 'error',
149
95
  message: 'No Docker images installed',
150
- detail: 'Required images have not been pulled',
151
96
  fix: 'Pull images with: /install',
152
97
  fixCommand: '/install',
153
98
  });
154
99
  }
155
100
  else {
101
+ const missing = status.missing.slice(0, 3).map(i => i.split('/').pop()).join(', ');
156
102
  results.push({
157
103
  name: 'Docker Images',
158
104
  severity: 'warning',
159
- message: `${imageStatus.installed.length}/${imageStatus.total} images installed`,
160
- detail: `Missing: ${imageStatus.missing.slice(0, 3).map(i => i.split('/').pop()).join(', ')}${imageStatus.missing.length > 3 ? '...' : ''}`,
105
+ message: `${status.installed.length}/${status.total} images installed`,
106
+ detail: `Missing: ${missing}${status.missing.length > 3 ? '...' : ''}`,
161
107
  fix: 'Pull missing images with: /install',
162
108
  fixCommand: '/install',
163
109
  });
164
110
  }
165
111
  }
166
- /**
167
- * Check ClawSQL API health
168
- */
169
112
  async function checkClawSQLAPI(ctx, results) {
170
113
  try {
171
114
  const response = await fetch(`http://localhost:${ctx.settings.api.port}/health`, {
172
115
  signal: AbortSignal.timeout(5000),
173
116
  });
174
- if (response.ok) {
175
- const data = await response.json();
176
- if (data.status === 'healthy') {
177
- results.push({
178
- name: 'ClawSQL API',
179
- severity: 'ok',
180
- message: `Running on port ${ctx.settings.api.port}`,
181
- });
182
- }
183
- else {
184
- results.push({
185
- name: 'ClawSQL API',
186
- severity: 'error',
187
- message: `API returned status: ${data.status}`,
188
- fix: 'Check logs: docker logs clawsql',
189
- });
190
- }
117
+ if (!response.ok) {
118
+ results.push({
119
+ name: 'ClawSQL API',
120
+ severity: 'error',
121
+ message: `API returned HTTP ${response.status}`,
122
+ fix: 'Check logs: docker logs clawsql',
123
+ });
124
+ return;
125
+ }
126
+ const data = await response.json();
127
+ if (data.status === 'healthy') {
128
+ results.push({ name: 'ClawSQL API', severity: 'ok', message: `Running on port ${ctx.settings.api.port}` });
191
129
  }
192
130
  else {
193
131
  results.push({
194
132
  name: 'ClawSQL API',
195
133
  severity: 'error',
196
- message: `API returned HTTP ${response.status}`,
134
+ message: `API returned status: ${data.status}`,
197
135
  fix: 'Check logs: docker logs clawsql',
198
136
  });
199
137
  }
@@ -209,54 +147,20 @@ async function checkClawSQLAPI(ctx, results) {
209
147
  });
210
148
  }
211
149
  }
212
- /**
213
- * Check Orchestrator health
214
- */
215
150
  async function checkOrchestrator(ctx, results) {
151
+ // Health check
216
152
  try {
217
153
  const isHealthy = await ctx.orchestrator.healthCheck();
218
- if (isHealthy) {
219
- results.push({
220
- name: 'Orchestrator',
221
- severity: 'ok',
222
- message: `Running at ${ctx.settings.orchestrator.url}`,
223
- });
224
- // Check if instances are discovered
225
- try {
226
- const clusters = await ctx.orchestrator.getClusters();
227
- if (clusters.length === 0) {
228
- results.push({
229
- name: 'MySQL Topology',
230
- severity: 'warning',
231
- message: 'No MySQL instances discovered in Orchestrator',
232
- fix: 'Register instances with: /instances register <host>',
233
- fixCommand: '/instances register <mysql-host>',
234
- });
235
- }
236
- else {
237
- results.push({
238
- name: 'MySQL Topology',
239
- severity: 'ok',
240
- message: `${clusters.length} cluster(s) discovered`,
241
- });
242
- }
243
- }
244
- catch {
245
- results.push({
246
- name: 'MySQL Topology',
247
- severity: 'warning',
248
- message: 'Could not retrieve topology information',
249
- });
250
- }
251
- }
252
- else {
154
+ if (!isHealthy) {
253
155
  results.push({
254
156
  name: 'Orchestrator',
255
157
  severity: 'error',
256
158
  message: 'Health check failed',
257
159
  fix: 'Check Orchestrator container: docker logs orchestrator',
258
160
  });
161
+ return;
259
162
  }
163
+ results.push({ name: 'Orchestrator', severity: 'ok', message: `Running at ${ctx.settings.orchestrator.url}` });
260
164
  }
261
165
  catch {
262
166
  results.push({
@@ -264,92 +168,63 @@ async function checkOrchestrator(ctx, results) {
264
168
  severity: 'error',
265
169
  message: 'Cannot connect to Orchestrator',
266
170
  detail: `Expected at ${ctx.settings.orchestrator.url}`,
267
- fix: 'Ensure Orchestrator container is running: docker ps | grep orchestrator',
171
+ fix: 'Ensure container is running: docker ps | grep orchestrator',
268
172
  });
269
173
  }
270
174
  }
271
- /**
272
- * Check ProxySQL health
273
- */
274
175
  async function checkProxySQL(ctx, results) {
275
176
  try {
276
177
  await ctx.proxysql.connect();
277
- results.push({
278
- name: 'ProxySQL',
279
- severity: 'ok',
280
- message: `Admin interface running on port ${ctx.settings.proxysql.adminPort}`,
281
- });
282
- // Check if MySQL servers are configured
283
- try {
284
- const servers = await ctx.proxysql.getServers();
285
- if (servers.length === 0) {
178
+ results.push({ name: 'ProxySQL', severity: 'ok', message: `Admin interface on port ${ctx.settings.proxysql.adminPort}` });
179
+ const servers = await ctx.proxysql.getServers();
180
+ if (servers.length === 0) {
181
+ results.push({
182
+ name: 'ProxySQL Servers',
183
+ severity: 'warning',
184
+ message: 'No MySQL servers configured',
185
+ fix: 'Register instances, then sync with: /clusters sync',
186
+ });
187
+ }
188
+ else {
189
+ const online = servers.filter(s => s.status === 'ONLINE').length;
190
+ if (online === servers.length) {
191
+ results.push({ name: 'ProxySQL Servers', severity: 'ok', message: `${online}/${servers.length} servers online` });
192
+ }
193
+ else {
286
194
  results.push({
287
195
  name: 'ProxySQL Servers',
288
196
  severity: 'warning',
289
- message: 'No MySQL servers configured in ProxySQL',
290
- fix: 'Register MySQL instances, then sync with: /clusters sync',
197
+ message: `${online}/${servers.length} servers online`,
198
+ fix: 'Check MySQL server connectivity and credentials',
291
199
  });
292
200
  }
293
- else {
294
- const onlineServers = servers.filter(s => s.status === 'ONLINE');
295
- if (onlineServers.length === servers.length) {
296
- results.push({
297
- name: 'ProxySQL Servers',
298
- severity: 'ok',
299
- message: `${onlineServers.length}/${servers.length} servers online`,
300
- });
301
- }
302
- else {
303
- results.push({
304
- name: 'ProxySQL Servers',
305
- severity: 'warning',
306
- message: `${onlineServers.length}/${servers.length} servers online`,
307
- fix: 'Check MySQL server connectivity and credentials',
308
- });
309
- }
310
- }
311
- }
312
- catch {
313
- results.push({
314
- name: 'ProxySQL Servers',
315
- severity: 'warning',
316
- message: 'Could not retrieve server configuration',
317
- });
318
201
  }
319
202
  await ctx.proxysql.close();
320
203
  }
321
204
  catch (error) {
322
- const message = error instanceof Error ? error.message : String(error);
323
205
  results.push({
324
206
  name: 'ProxySQL',
325
207
  severity: 'error',
326
208
  message: 'Cannot connect to ProxySQL admin interface',
327
- detail: message,
328
- fix: 'Ensure ProxySQL container is running: docker ps | grep proxysql',
209
+ detail: error instanceof Error ? error.message : String(error),
210
+ fix: 'Ensure container is running: docker ps | grep proxysql',
329
211
  });
330
212
  }
331
213
  }
332
- /**
333
- * Check Prometheus health
334
- */
335
214
  async function checkPrometheus(ctx, results) {
336
215
  try {
337
216
  const response = await fetch(`${ctx.settings.prometheus.url}/-/healthy`, {
338
217
  signal: AbortSignal.timeout(5000),
339
218
  });
340
219
  if (response.ok) {
341
- results.push({
342
- name: 'Prometheus',
343
- severity: 'ok',
344
- message: `Running at ${ctx.settings.prometheus.url}`,
345
- });
220
+ results.push({ name: 'Prometheus', severity: 'ok', message: `Running at ${ctx.settings.prometheus.url}` });
346
221
  }
347
222
  else {
348
223
  results.push({
349
224
  name: 'Prometheus',
350
225
  severity: 'warning',
351
226
  message: `Health check returned status ${response.status}`,
352
- fix: 'Check Prometheus container: docker logs prometheus',
227
+ fix: 'Check container: docker logs prometheus',
353
228
  });
354
229
  }
355
230
  }
@@ -363,109 +238,130 @@ async function checkPrometheus(ctx, results) {
363
238
  });
364
239
  }
365
240
  }
366
- /**
367
- * Check OpenClaw AI Gateway health
368
- */
369
241
  async function checkOpenClaw(_ctx, results) {
370
242
  try {
371
- const status = await (0, openclaw_integration_js_1.getOpenClawStatus)();
372
- if (status.available) {
373
- // Check gateway health
374
- const gatewayHealthy = await (0, openclaw_integration_js_1.isGatewayHealthy)();
375
- if (gatewayHealthy) {
376
- if (status.isDocker) {
377
- results.push({
378
- name: 'OpenClaw Gateway',
379
- severity: 'ok',
380
- message: 'Running in Docker container (ws://localhost:18789)',
381
- });
382
- }
383
- else if (status.isLocal) {
243
+ const status = await (0, openclaw_integration_js_1.getDetailedOpenClawStatus)();
244
+ if (!status.available) {
245
+ // Check if container exists but stopped
246
+ const runtime = await (0, docker_prereq_js_1.detectRuntime)();
247
+ if (runtime) {
248
+ const psResult = await execCommand([runtime, 'ps', '-a', '--filter', 'name=openclaw', '--format', '{{.Status}}'], true);
249
+ const containerStatus = psResult.stdout.trim();
250
+ if (containerStatus && !containerStatus.toLowerCase().includes('up')) {
384
251
  results.push({
385
252
  name: 'OpenClaw Gateway',
386
- severity: 'ok',
387
- message: 'Using local installation (ws://localhost:18789)',
253
+ severity: 'warning',
254
+ message: 'OpenClaw container exists but is not running',
255
+ detail: `Status: ${containerStatus}`,
256
+ fix: 'Restart the platform with: /start',
257
+ fixCommand: '/start',
388
258
  });
259
+ return;
389
260
  }
390
261
  }
262
+ results.push({
263
+ name: 'OpenClaw Gateway',
264
+ severity: 'warning',
265
+ message: 'OpenClaw gateway is not available',
266
+ detail: 'AI features will not work without OpenClaw',
267
+ fix: 'Start with: /start',
268
+ fixCommand: '/start',
269
+ });
270
+ return;
271
+ }
272
+ if (!status.gatewayHealthy) {
273
+ results.push({
274
+ name: 'OpenClaw Gateway',
275
+ severity: 'warning',
276
+ message: 'Gateway is not responding',
277
+ fix: 'Check logs: docker logs openclaw',
278
+ });
279
+ return;
280
+ }
281
+ const modeDisplay = status.mode === 'docker' ? 'Running in Docker' : 'Using local installation';
282
+ results.push({ name: 'OpenClaw Gateway', severity: 'ok', message: `${modeDisplay} (ws://localhost:18789)` });
283
+ // Check for auto-detected AI config from environment
284
+ const aiConfig = (0, ai_config_js_1.detectAIConfigFromEnv)();
285
+ // Model info
286
+ if (status.modelInfo.configured && status.modelInfo.model) {
287
+ results.push({
288
+ name: 'OpenClaw Model',
289
+ severity: 'ok',
290
+ message: `${status.modelInfo.provider || 'custom'}: ${status.modelInfo.model}`,
291
+ });
292
+ }
293
+ else if (aiConfig.provider !== 'none') {
294
+ results.push({
295
+ name: 'OpenClaw Model',
296
+ severity: 'ok',
297
+ message: `${aiConfig.provider}${aiConfig.model ? '/' + aiConfig.model : ''} (auto-detected)`,
298
+ });
299
+ }
300
+ else {
301
+ results.push({
302
+ name: 'OpenClaw Model',
303
+ severity: 'info',
304
+ message: 'Using bundled qwen model (limited)',
305
+ fix: 'Set ANTHROPIC_API_KEY or OPENAI_API_KEY for better AI',
306
+ });
307
+ }
308
+ // Quick AI test - optional, can be slow
309
+ // Use a simple gateway health check instead of full AI query
310
+ const httpUrl = openclaw_integration_js_1.CONFIG.gatewayUrl.replace('ws://', 'http://').replace('wss://', 'https://');
311
+ try {
312
+ const start = Date.now();
313
+ const response = await fetch(`${httpUrl}/health`, {
314
+ signal: AbortSignal.timeout(5000),
315
+ });
316
+ const latency = Date.now() - start;
317
+ if (response.ok) {
318
+ results.push({ name: 'OpenClaw AI Test', severity: 'ok', message: `Gateway healthy (${latency}ms)` });
319
+ }
391
320
  else {
392
321
  results.push({
393
- name: 'OpenClaw Gateway',
322
+ name: 'OpenClaw AI Test',
394
323
  severity: 'warning',
395
- message: 'Gateway is not responding',
396
- detail: 'Container is running but gateway health check failed',
324
+ message: `Gateway returned ${response.status}`,
397
325
  fix: 'Check logs: docker logs openclaw',
398
326
  });
399
327
  }
400
328
  }
401
- else {
402
- // Check if Docker OpenClaw container exists but is not running
403
- const runtime = await (0, docker_prereq_js_1.detectRuntime)();
404
- if (runtime) {
405
- try {
406
- const psResult = await execCommand([runtime, 'ps', '-a', '--filter', 'name=openclaw', '--format', '{{.Status}}'], true);
407
- const containerStatus = psResult.stdout.trim();
408
- if (containerStatus && !containerStatus.toLowerCase().includes('up')) {
409
- results.push({
410
- name: 'OpenClaw Gateway',
411
- severity: 'warning',
412
- message: 'OpenClaw container exists but is not running',
413
- detail: `Container status: ${containerStatus}`,
414
- fix: 'Restart the platform with: /start',
415
- fixCommand: '/start',
416
- });
417
- return;
418
- }
419
- }
420
- catch {
421
- // Continue to default message
422
- }
423
- }
329
+ catch {
424
330
  results.push({
425
- name: 'OpenClaw Gateway',
331
+ name: 'OpenClaw AI Test',
426
332
  severity: 'warning',
427
- message: 'OpenClaw gateway is not available',
428
- detail: 'AI-powered features will not work without OpenClaw',
429
- fix: 'Start the platform with: /start to launch OpenClaw in Docker',
430
- fixCommand: '/start',
333
+ message: 'Gateway health check failed',
334
+ fix: 'Check logs: docker logs openclaw',
431
335
  });
432
336
  }
433
337
  }
434
338
  catch (error) {
435
- const message = error instanceof Error ? error.message : String(error);
436
339
  results.push({
437
340
  name: 'OpenClaw Gateway',
438
341
  severity: 'warning',
439
- message: 'Could not check OpenClaw status',
440
- detail: message,
441
- fix: 'Ensure OpenClaw container is running: docker ps | grep openclaw',
342
+ message: 'Could not check status',
343
+ detail: error instanceof Error ? error.message : String(error),
344
+ fix: 'Ensure container is running: docker ps | grep openclaw',
442
345
  });
443
346
  }
444
347
  }
445
- /**
446
- * Check configuration issues
447
- */
448
348
  function checkConfiguration(ctx, results) {
449
- // Check API token secret
450
349
  if (ctx.settings.api.tokenSecret === 'change-me-in-production') {
451
350
  results.push({
452
351
  name: 'API Token Secret',
453
352
  severity: 'warning',
454
353
  message: 'Using default token secret (not secure for production)',
455
- fix: 'Set environment variable: API_TOKEN_SECRET=<your-secret>',
354
+ fix: 'Set: API_TOKEN_SECRET=<your-secret>',
456
355
  });
457
356
  }
458
- // Check MySQL credentials
459
357
  if (!ctx.settings.mysql.adminPassword) {
460
358
  results.push({
461
359
  name: 'MySQL Credentials',
462
360
  severity: 'warning',
463
361
  message: 'MySQL admin password not configured',
464
- detail: 'Required for instance discovery and management',
465
- fix: 'Set environment variable: MYSQL_ADMIN_PASSWORD=<password>',
362
+ fix: 'Set: MYSQL_ADMIN_PASSWORD=<password>',
466
363
  });
467
364
  }
468
- // Check auto-failover
469
365
  if (!ctx.settings.failover.autoFailoverEnabled) {
470
366
  results.push({
471
367
  name: 'Auto Failover',
@@ -474,333 +370,88 @@ function checkConfiguration(ctx, results) {
474
370
  fix: 'Enable with: AUTO_FAILOVER_ENABLED=true',
475
371
  });
476
372
  }
477
- // Check metadata database
478
373
  if (!ctx.settings.metadataDb.host) {
479
- results.push({
480
- name: 'Metadata Database',
481
- severity: 'ok',
482
- message: 'Using auto-provisioned metadata-mysql container',
483
- });
484
- }
485
- }
486
- /**
487
- * Check MySQL instances health
488
- */
489
- async function checkMySQLInstances(ctx, results) {
490
- try {
491
- const clusters = await ctx.orchestrator.getClusters();
492
- for (const clusterName of clusters) {
493
- const topology = await ctx.orchestrator.getTopology(clusterName);
494
- if (!topology)
495
- continue;
496
- // Check primary
497
- if (topology.primary) {
498
- if (topology.primary.state !== 'online') {
499
- results.push({
500
- name: `Primary [${topology.name || clusterName}]`,
501
- severity: 'error',
502
- message: `Primary ${topology.primary.host}:${topology.primary.port} is ${topology.primary.state}`,
503
- fix: 'Check MySQL instance status and connectivity',
504
- });
505
- }
506
- else {
507
- results.push({
508
- name: `Primary [${topology.name || clusterName}]`,
509
- severity: 'ok',
510
- message: `${topology.primary.host}:${topology.primary.port} is online`,
511
- });
512
- }
513
- }
514
- else {
515
- results.push({
516
- name: `Primary [${topology.name || clusterName}]`,
517
- severity: 'error',
518
- message: 'No primary found for cluster',
519
- fix: 'Check replication setup or promote a replica: /failover switchover',
520
- });
521
- }
522
- // Check replicas
523
- for (const replica of topology.replicas) {
524
- if (replica.state !== 'online') {
525
- results.push({
526
- name: `Replica [${topology.name || clusterName}]`,
527
- severity: 'warning',
528
- message: `Replica ${replica.host}:${replica.port} is ${replica.state}`,
529
- fix: 'Check replica MySQL status and replication connection',
530
- });
531
- }
532
- else if (replica.replicationLag !== undefined && replica.replicationLag > 60) {
533
- results.push({
534
- name: `Replica Lag [${topology.name || clusterName}]`,
535
- severity: 'warning',
536
- message: `Replica ${replica.host}:${replica.port} has high lag (${replica.replicationLag}s)`,
537
- fix: 'Check replica performance and network connectivity',
538
- });
539
- }
540
- }
541
- }
542
- }
543
- catch {
544
- // Already handled in Orchestrator check
374
+ results.push({ name: 'Metadata Database', severity: 'ok', message: 'Using auto-provisioned container' });
545
375
  }
546
376
  }
547
- /**
548
- * Check replication topology issues
549
- */
550
- async function checkReplicationTopology(ctx, results) {
551
- try {
552
- const analysis = await ctx.orchestrator.getReplicationAnalysis();
553
- for (const issue of analysis) {
554
- const analysisType = issue.Analysis;
555
- const description = issue.Description;
556
- const affectedHost = issue.Key?.Hostname;
557
- if (analysisType && !analysisType.includes('NoProblem')) {
558
- results.push({
559
- name: 'Replication Analysis',
560
- severity: 'warning',
561
- message: `${analysisType}: ${affectedHost || 'unknown'}`,
562
- detail: description,
563
- fix: 'Review Orchestrator UI for details: http://localhost:3000',
564
- });
565
- }
566
- }
567
- }
568
- catch {
569
- // Orchestrator may not be available
570
- }
571
- }
572
- /**
573
- * Check ProxySQL sync with Orchestrator topology
574
- */
575
- async function checkProxySQLSync(ctx, results) {
576
- try {
577
- // Get topology from Orchestrator
578
- const clusters = await ctx.orchestrator.getClusters();
579
- if (clusters.length === 0) {
580
- return; // No clusters to sync
581
- }
582
- // Get ProxySQL server stats and hostgroups
583
- let proxysqlServers = [];
584
- let hostgroups = [];
585
- try {
586
- await ctx.proxysql.connect();
587
- proxysqlServers = await ctx.proxysql.getServers();
588
- hostgroups = await ctx.proxysql.getReplicationHostgroups();
589
- await ctx.proxysql.close();
590
- }
591
- catch {
592
- // ProxySQL not available - already reported in checkProxySQL
593
- return;
594
- }
595
- // Build expected instances from Orchestrator
596
- const expectedInstances = new Map();
597
- for (const clusterName of clusters) {
598
- const topology = await ctx.orchestrator.getTopology(clusterName);
599
- if (!topology)
600
- continue;
601
- if (topology.primary) {
602
- const key = `${topology.primary.host}:${topology.primary.port}`;
603
- expectedInstances.set(key, {
604
- host: topology.primary.host,
605
- port: topology.primary.port,
606
- role: 'primary',
607
- cluster: topology.name || clusterName,
608
- });
609
- }
610
- for (const replica of topology.replicas) {
611
- const key = `${replica.host}:${replica.port}`;
612
- expectedInstances.set(key, {
613
- host: replica.host,
614
- port: replica.port,
615
- role: 'replica',
616
- cluster: topology.name || clusterName,
617
- });
618
- }
619
- }
620
- // Build actual ProxySQL instances
621
- const proxysqlInstances = new Map();
622
- for (const server of proxysqlServers) {
623
- const key = `${server.hostname}:${server.port}`;
624
- proxysqlInstances.set(key, {
625
- host: server.hostname,
626
- port: server.port,
627
- hostgroup: server.hostgroupId,
628
- status: server.status,
629
- });
630
- }
631
- // Determine default hostgroups (coerce to numbers)
632
- const defaultWriterHG = hostgroups.length > 0 ? Number(hostgroups[0].writerHostgroup) : 10;
633
- const defaultReaderHG = hostgroups.length > 0 ? Number(hostgroups[0].readerHostgroup) : 20;
634
- // Check for missing instances in ProxySQL
635
- const missingInProxySQL = [];
636
- const wrongHostgroup = [];
637
- for (const [key, instance] of expectedInstances) {
638
- const proxysqlInstance = proxysqlInstances.get(key);
639
- if (!proxysqlInstance) {
640
- missingInProxySQL.push(`${key} (${instance.role})`);
641
- }
642
- else {
643
- // Check if hostgroup is correct (coerce both to numbers for comparison)
644
- const actualHG = Number(proxysqlInstance.hostgroup);
645
- const expectedHG = instance.role === 'primary' ? defaultWriterHG : defaultReaderHG;
646
- // Skip if both are NaN or if they match
647
- if (!isNaN(actualHG) && !isNaN(expectedHG) && actualHG !== expectedHG) {
648
- wrongHostgroup.push(`${key} is in hg:${actualHG}, expected hg:${expectedHG}`);
649
- }
650
- }
651
- }
652
- // Check for orphan instances in ProxySQL (not in Orchestrator)
653
- const orphanInstances = [];
654
- for (const [key, instance] of proxysqlInstances) {
655
- if (!expectedInstances.has(key)) {
656
- orphanInstances.push(`${key} (hg:${instance.hostgroup})`);
657
- }
658
- }
659
- // Report findings
660
- if (missingInProxySQL.length > 0) {
661
- results.push({
662
- name: 'ProxySQL Sync',
663
- severity: 'warning',
664
- message: `${missingInProxySQL.length} instance(s) from Orchestrator missing in ProxySQL`,
665
- detail: missingInProxySQL.slice(0, 5).join(', ') + (missingInProxySQL.length > 5 ? '...' : ''),
666
- fix: 'Sync clusters to ProxySQL with: /clusters sync',
667
- fixCommand: '/clusters sync',
668
- });
669
- }
670
- if (wrongHostgroup.length > 0) {
671
- results.push({
672
- name: 'ProxySQL Hostgroups',
673
- severity: 'warning',
674
- message: `${wrongHostgroup.length} instance(s) in wrong hostgroup`,
675
- detail: wrongHostgroup.slice(0, 3).join('; '),
676
- fix: 'Re-sync clusters to ProxySQL with: /clusters sync',
677
- fixCommand: '/clusters sync',
678
- });
679
- }
680
- if (orphanInstances.length > 0) {
681
- results.push({
682
- name: 'ProxySQL Orphans',
683
- severity: 'warning',
684
- message: `${orphanInstances.length} instance(s) in ProxySQL not in Orchestrator`,
685
- detail: orphanInstances.slice(0, 3).join(', '),
686
- fix: 'Remove orphan servers from ProxySQL or register them in Orchestrator',
687
- });
688
- }
689
- // All synced properly
690
- if (missingInProxySQL.length === 0 && wrongHostgroup.length === 0 && orphanInstances.length === 0 && expectedInstances.size > 0) {
691
- results.push({
692
- name: 'ProxySQL Sync',
693
- severity: 'ok',
694
- message: `All ${expectedInstances.size} instances properly synced`,
695
- });
696
- }
697
- }
698
- catch (error) {
699
- const message = error instanceof Error ? error.message : String(error);
700
- results.push({
701
- name: 'ProxySQL Sync',
702
- severity: 'warning',
703
- message: 'Could not verify ProxySQL sync',
704
- detail: message,
705
- });
706
- }
707
- }
708
- /**
709
- * Display diagnostic results
710
- */
377
+ // ============================================================================
378
+ // Display Utilities
379
+ // ============================================================================
711
380
  function displayResults(results) {
712
- // Group by severity
713
- const errors = results.filter(r => r.severity === 'error');
714
- const warnings = results.filter(r => r.severity === 'warning');
715
- const ok = results.filter(r => r.severity === 'ok');
716
- const info = results.filter(r => r.severity === 'info');
717
- // Display errors first
718
- if (errors.length > 0) {
719
- console.log(components_js_1.theme.error.bold('\n✗ Errors:\n'));
720
- for (const result of errors) {
721
- displayResult(result);
722
- }
381
+ const groups = {
382
+ error: [],
383
+ warning: [],
384
+ info: [],
385
+ ok: [],
386
+ };
387
+ for (const r of results) {
388
+ groups[r.severity].push(r);
723
389
  }
724
- // Display warnings
725
- if (warnings.length > 0) {
726
- console.log(components_js_1.theme.warning.bold('\n◆ Warnings:\n'));
727
- for (const result of warnings) {
728
- displayResult(result);
729
- }
390
+ if (groups.error.length > 0) {
391
+ console.log(components_js_1.theme.error.bold('✗ Errors:'));
392
+ groups.error.forEach(displayResult);
730
393
  }
731
- // Display info
732
- if (info.length > 0) {
733
- console.log(components_js_1.theme.info.bold('\n○ Information:\n'));
734
- for (const result of info) {
735
- displayResult(result);
736
- }
394
+ if (groups.warning.length > 0) {
395
+ console.log(components_js_1.theme.warning.bold('◆ Warnings:'));
396
+ groups.warning.forEach(displayResult);
737
397
  }
738
- // Display healthy checks
739
- if (ok.length > 0) {
740
- console.log(components_js_1.theme.success.bold('\n✓ Healthy:\n'));
741
- for (const result of ok) {
742
- console.log(components_js_1.theme.muted(` ${result.name}: `) + components_js_1.theme.success(result.message));
743
- }
398
+ if (groups.info.length > 0) {
399
+ console.log(components_js_1.theme.info.bold('○ Information:'));
400
+ groups.info.forEach(displayResult);
401
+ }
402
+ if (groups.ok.length > 0) {
403
+ console.log(components_js_1.theme.success.bold('✓ Healthy:'));
404
+ groups.ok.forEach(r => console.log(components_js_1.theme.muted(` ${r.name}: `) + components_js_1.theme.success(r.message)));
744
405
  }
745
406
  }
746
- /**
747
- * Display a single diagnostic result
748
- */
749
407
  function displayResult(result) {
750
- const severityStyles = {
751
- error: components_js_1.theme.error,
752
- warning: components_js_1.theme.warning,
753
- ok: components_js_1.theme.success,
754
- info: components_js_1.theme.info,
755
- };
756
- const severityIcons = {
408
+ const icons = {
757
409
  error: components_js_1.theme.error(components_js_1.indicators.cross),
758
410
  warning: components_js_1.theme.warning(components_js_1.indicators.warning),
759
- ok: components_js_1.theme.success(components_js_1.indicators.check),
760
411
  info: components_js_1.theme.info(components_js_1.indicators.info),
412
+ ok: components_js_1.theme.success(components_js_1.indicators.check),
761
413
  };
762
- const icon = severityIcons[result.severity];
763
- const nameColor = severityStyles[result.severity];
764
- console.log(` ${icon} ${nameColor.bold(result.name)}: ${result.message}`);
765
- if (result.detail) {
414
+ const colorFn = {
415
+ error: components_js_1.theme.error,
416
+ warning: components_js_1.theme.warning,
417
+ info: components_js_1.theme.info,
418
+ ok: components_js_1.theme.success,
419
+ }[result.severity];
420
+ console.log(` ${icons[result.severity]} ${colorFn.bold(result.name)}: ${result.message}`);
421
+ if (result.detail)
766
422
  console.log(components_js_1.theme.muted(` ${result.detail}`));
767
- }
768
- if (result.fix) {
423
+ if (result.fix)
769
424
  console.log(components_js_1.theme.primary(` Fix: ${result.fix}`));
770
- }
771
- if (result.fixCommand) {
425
+ if (result.fixCommand)
772
426
  console.log(components_js_1.theme.muted(` Command: ${result.fixCommand}`));
427
+ }
428
+ function printSummary(results) {
429
+ const errors = results.filter(r => r.severity === 'error').length;
430
+ const warnings = results.filter(r => r.severity === 'warning').length;
431
+ if (errors === 0 && warnings === 0) {
432
+ console.log(components_js_1.theme.success(`${components_js_1.indicators.check} All systems healthy!`));
773
433
  }
434
+ else {
435
+ console.log(components_js_1.theme.warning(`Found ${errors} error(s) and ${warnings} warning(s)`));
436
+ }
437
+ // Add hint for MySQL cluster details
774
438
  console.log();
439
+ console.log(components_js_1.theme.muted(' For MySQL cluster details, use: /topology or /clusters'));
775
440
  }
776
- /**
777
- * Execute a shell command
778
- */
441
+ // ============================================================================
442
+ // Process Execution
443
+ // ============================================================================
779
444
  function execCommand(cmd, silent = false) {
780
445
  return new Promise((resolve) => {
781
- const proc = (0, child_process_1.spawn)(cmd[0], cmd.slice(1), {
782
- stdio: silent ? 'pipe' : 'inherit',
783
- });
446
+ const proc = (0, child_process_1.spawn)(cmd[0], cmd.slice(1), { stdio: silent ? 'pipe' : 'inherit' });
784
447
  let stdout = '';
785
448
  let stderr = '';
786
449
  if (silent) {
787
450
  proc.stdout?.on('data', (data) => { stdout += data; });
788
451
  proc.stderr?.on('data', (data) => { stderr += data; });
789
452
  }
790
- proc.on('close', (code) => {
791
- resolve({
792
- success: code === 0,
793
- stdout,
794
- stderr,
795
- });
796
- });
797
- proc.on('error', () => {
798
- resolve({
799
- success: false,
800
- stdout: '',
801
- stderr: 'Failed to execute command',
802
- });
803
- });
453
+ proc.on('close', (code) => resolve({ success: code === 0, stdout, stderr }));
454
+ proc.on('error', () => resolve({ success: false, stdout: '', stderr: 'Failed to execute' }));
804
455
  });
805
456
  }
806
457
  exports.default = exports.doctorCommand;