clawsql 0.2.1 → 0.2.2

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