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
@@ -39,7 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  };
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.isOpenClawGatewayReachable = exports.isDockerOpenClawContainerRunning = exports.OpenClawAgent = void 0;
42
+ exports.isOpenClawGatewayReachable = exports.isDockerOpenClawContainerRunning = exports.SUPPORTED_PROVIDERS = exports.OpenClawAgent = exports.CONFIG = void 0;
43
43
  exports.isDockerOpenClawAvailable = isDockerOpenClawAvailable;
44
44
  exports.isGatewayHealthy = isGatewayHealthy;
45
45
  exports.isLocalOpenClawAvailable = isLocalOpenClawAvailable;
@@ -51,27 +51,45 @@ exports.scheduleCron = scheduleCron;
51
51
  exports.sendNotification = sendNotification;
52
52
  exports.writeToMemory = writeToMemory;
53
53
  exports.createOpenClawAgent = createOpenClawAgent;
54
+ exports.getModelProviderInfo = getModelProviderInfo;
55
+ exports.configureModelProvider = configureModelProvider;
56
+ exports.testOpenClawConnection = testOpenClawConnection;
57
+ exports.getDetailedOpenClawStatus = getDetailedOpenClawStatus;
54
58
  exports.ensureOpenClawRunning = ensureOpenClawRunning;
55
59
  const child_process_1 = require("child_process");
56
60
  const fs = __importStar(require("fs"));
57
61
  const path = __importStar(require("path"));
58
62
  const os = __importStar(require("os"));
63
+ const docker_prereq_js_1 = require("../utils/docker-prereq.js");
59
64
  // ============================================================================
60
65
  // Configuration
61
66
  // ============================================================================
62
- const DEFAULT_GATEWAY_URL = 'ws://localhost:18789';
63
- const DEFAULT_GATEWAY_TOKEN = 'clawsql-openclaw-token';
64
- const SESSION_ID = 'clawsql-session';
65
- const DEFAULT_TIMEOUT = 120000;
66
- const getGatewayUrl = () => process.env.OPENCLAW_GATEWAY_URL || DEFAULT_GATEWAY_URL;
67
- const getGatewayToken = () => process.env.OPENCLAW_GATEWAY_TOKEN || DEFAULT_GATEWAY_TOKEN;
67
+ exports.CONFIG = {
68
+ gatewayUrl: process.env.OPENCLAW_GATEWAY_URL || 'ws://localhost:18789',
69
+ gatewayToken: process.env.OPENCLAW_GATEWAY_TOKEN || 'clawsql-openclaw-token',
70
+ sessionId: 'clawsql-session',
71
+ defaultTimeout: 120000,
72
+ healthCheckTimeout: 5000,
73
+ containerName: 'openclaw',
74
+ };
75
+ // Cached runtime detection
76
+ let cachedRuntime;
77
+ async function getContainerRuntime() {
78
+ if (cachedRuntime === undefined) {
79
+ cachedRuntime = await (0, docker_prereq_js_1.detectRuntime)();
80
+ }
81
+ return cachedRuntime;
82
+ }
83
+ // ============================================================================
84
+ // Process Execution Utilities
85
+ // ============================================================================
68
86
  /**
69
87
  * Execute a command and return the result
70
88
  */
71
89
  function execCommand(command, args, options) {
72
90
  return new Promise((resolve) => {
73
91
  const proc = (0, child_process_1.spawn)(command, args, {
74
- timeout: options?.timeout || 30000,
92
+ timeout: options?.timeout ?? 30000,
75
93
  stdio: ['ignore', 'pipe', 'pipe'],
76
94
  env: options?.env ? { ...process.env, ...options.env } : process.env,
77
95
  });
@@ -88,39 +106,146 @@ function execCommand(command, args, options) {
88
106
  });
89
107
  }
90
108
  /**
91
- * Execute openclaw CLI with gateway configuration
109
+ * Get spawn configuration for OpenClaw CLI execution
110
+ * Automatically uses container exec when OpenClaw is running in Docker/Podman
92
111
  */
93
- async function execOpenClaw(args, options) {
94
- const gatewayUrl = options?.gatewayUrl || getGatewayUrl();
95
- const gatewayToken = options?.gatewayToken || getGatewayToken();
96
- const env = {};
97
- if (gatewayUrl !== DEFAULT_GATEWAY_URL) {
98
- env.OPENCLAW_GATEWAY_URL = gatewayUrl;
112
+ async function getSpawnConfig(args) {
113
+ const isDocker = await isDockerOpenClawAvailable();
114
+ const runtime = isDocker ? await getContainerRuntime() : null;
115
+ // Environment variables to pass to OpenClaw
116
+ // Include all AI-related env vars for seamless integration
117
+ const env = {
118
+ OPENCLAW_GATEWAY_URL: exports.CONFIG.gatewayUrl,
119
+ OPENCLAW_GATEWAY_TOKEN: exports.CONFIG.gatewayToken,
120
+ // Anthropic/Claude configuration
121
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? '',
122
+ ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL ?? '',
123
+ ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL ?? '',
124
+ // OpenAI configuration
125
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '',
126
+ OPENAI_BASE_URL: process.env.OPENAI_BASE_URL ?? '',
127
+ OPENAI_MODEL: process.env.OPENAI_MODEL ?? '',
128
+ };
129
+ if (runtime) {
130
+ // Execute inside container - pass env vars via -e flags
131
+ return {
132
+ command: runtime,
133
+ args: [
134
+ 'exec',
135
+ '-e', 'OPENCLAW_GATEWAY_URL',
136
+ '-e', 'OPENCLAW_GATEWAY_TOKEN',
137
+ '-e', 'ANTHROPIC_API_KEY',
138
+ '-e', 'ANTHROPIC_BASE_URL',
139
+ '-e', 'ANTHROPIC_MODEL',
140
+ '-e', 'OPENAI_API_KEY',
141
+ '-e', 'OPENAI_BASE_URL',
142
+ '-e', 'OPENAI_MODEL',
143
+ exports.CONFIG.containerName,
144
+ 'openclaw',
145
+ ...args,
146
+ ],
147
+ env,
148
+ };
99
149
  }
100
- env.OPENCLAW_GATEWAY_TOKEN = gatewayToken;
101
- return execCommand('openclaw', args, { timeout: options?.timeout, env });
150
+ // Execute local CLI
151
+ return {
152
+ command: 'openclaw',
153
+ args,
154
+ env,
155
+ };
156
+ }
157
+ /**
158
+ * Spawn OpenClaw process with proper configuration
159
+ * Filters out internal log messages from stdout
160
+ */
161
+ function spawnOpenClaw(args, options) {
162
+ return new Promise(async (resolve, reject) => {
163
+ const spawnConfig = await getSpawnConfig(args);
164
+ const timeout = options?.timeout ?? exports.CONFIG.defaultTimeout;
165
+ const proc = (0, child_process_1.spawn)(spawnConfig.command, spawnConfig.args, {
166
+ timeout,
167
+ stdio: ['ignore', 'pipe', 'pipe'],
168
+ env: spawnConfig.env,
169
+ });
170
+ let stdout = '';
171
+ let stderr = '';
172
+ /**
173
+ * Filter out OpenClaw internal log lines from output
174
+ * Log lines start with [bracket] pattern like [agents/...] or [xai-auth]
175
+ */
176
+ const filterLogLines = (text) => {
177
+ return text.split('\n')
178
+ .filter(line => !line.trim().startsWith('['))
179
+ .join('\n');
180
+ };
181
+ proc.stdout?.on('data', (data) => {
182
+ const chunk = data.toString();
183
+ stdout += chunk;
184
+ // Filter log lines when streaming to user
185
+ const filtered = filterLogLines(chunk);
186
+ if (filtered) {
187
+ options?.onChunk?.(filtered);
188
+ }
189
+ });
190
+ proc.stderr?.on('data', (data) => { stderr += data; });
191
+ // Abort signal handling
192
+ const abortHandler = () => {
193
+ proc.kill('SIGKILL');
194
+ reject(new DOMException('The operation was aborted', 'AbortError'));
195
+ };
196
+ if (options?.signal) {
197
+ if (options.signal.aborted) {
198
+ proc.kill('SIGKILL');
199
+ reject(new DOMException('The operation was aborted', 'AbortError'));
200
+ return;
201
+ }
202
+ options.signal.addEventListener('abort', abortHandler);
203
+ }
204
+ proc.on('close', (code) => {
205
+ options?.signal?.removeEventListener('abort', abortHandler);
206
+ if (code === 0) {
207
+ // Filter log lines from final output
208
+ resolve(filterLogLines(stdout).trim());
209
+ }
210
+ else if (code === null) {
211
+ reject(new DOMException('The operation was aborted', 'AbortError'));
212
+ }
213
+ else {
214
+ reject(new Error(`OpenClaw failed (exit ${code}): ${stderr || stdout}`));
215
+ }
216
+ });
217
+ proc.on('error', (err) => {
218
+ options?.signal?.removeEventListener('abort', abortHandler);
219
+ reject(new Error(`Failed to spawn openclaw: ${err.message}`));
220
+ });
221
+ });
102
222
  }
103
223
  // ============================================================================
104
224
  // Detection Functions
105
225
  // ============================================================================
106
226
  /**
107
- * Check if OpenClaw Docker container is running
227
+ * Check if OpenClaw container is running
108
228
  */
109
229
  async function isDockerOpenClawAvailable() {
110
- const result = await execCommand('docker', [
111
- 'ps', '--filter', 'name=openclaw', '--filter', 'status=running',
112
- '--format', '{{.Names}}'
230
+ const runtime = await getContainerRuntime();
231
+ if (!runtime)
232
+ return false;
233
+ const result = await execCommand(runtime, [
234
+ 'ps',
235
+ '--filter', `name=${exports.CONFIG.containerName}`,
236
+ '--filter', 'status=running',
237
+ '--format', '{{.Names}}',
113
238
  ]);
114
- return result.success && result.stdout.trim() === 'openclaw';
239
+ return result.success && result.stdout.trim() === exports.CONFIG.containerName;
115
240
  }
116
241
  /**
117
242
  * Check if OpenClaw gateway health endpoint responds
118
243
  */
119
244
  async function isGatewayHealthy() {
120
245
  try {
121
- const httpUrl = getGatewayUrl().replace('ws://', 'http://').replace('wss://', 'https://');
246
+ const httpUrl = exports.CONFIG.gatewayUrl.replace('ws://', 'http://').replace('wss://', 'https://');
122
247
  const response = await fetch(`${httpUrl}/health`, {
123
- signal: AbortSignal.timeout(5000),
248
+ signal: AbortSignal.timeout(exports.CONFIG.healthCheckTimeout),
124
249
  });
125
250
  return response.ok;
126
251
  }
@@ -129,22 +254,16 @@ async function isGatewayHealthy() {
129
254
  }
130
255
  }
131
256
  /**
132
- * Check if local OpenClaw CLI gateway is running (not Docker)
257
+ * Check if local OpenClaw CLI is running (not in container)
133
258
  */
134
259
  async function isLocalOpenClawAvailable() {
135
- // Docker container takes precedence
136
- if (await isDockerOpenClawAvailable()) {
260
+ if (await isDockerOpenClawAvailable())
137
261
  return false;
138
- }
139
- // Gateway must be reachable
140
- if (!(await isGatewayHealthy())) {
262
+ if (!(await isGatewayHealthy()))
141
263
  return false;
142
- }
143
- // Verify via CLI status
144
- const result = await execCommand('openclaw', ['status', '--json'], { timeout: 5000 });
145
- if (!result.success) {
264
+ const result = await execCommand('openclaw', ['status', '--json'], { timeout: exports.CONFIG.healthCheckTimeout });
265
+ if (!result.success)
146
266
  return false;
147
- }
148
267
  try {
149
268
  const status = JSON.parse(result.stdout);
150
269
  return status.gateway?.mode === 'local' && !!status.gateway?.url;
@@ -154,255 +273,253 @@ async function isLocalOpenClawAvailable() {
154
273
  }
155
274
  }
156
275
  /**
157
- * Check if any OpenClaw gateway is available (Docker or local)
276
+ * Check if any OpenClaw is available
277
+ * Optimized: checks gateway health first (fast HTTP), then container status
158
278
  */
159
279
  async function isOpenClawAvailable() {
160
- return (await isDockerOpenClawAvailable()) || (await isLocalOpenClawAvailable());
280
+ // Fast check: gateway health endpoint (no subprocess)
281
+ if (await isGatewayHealthy()) {
282
+ return true;
283
+ }
284
+ // Fallback: check if container is running (slower, spawns subprocess)
285
+ return isDockerOpenClawAvailable();
161
286
  }
162
287
  /**
163
- * Get detailed OpenClaw status
288
+ * Get OpenClaw status summary
164
289
  */
165
290
  async function getOpenClawStatus() {
166
291
  const [isDocker, isLocal] = await Promise.all([
167
292
  isDockerOpenClawAvailable(),
168
293
  isLocalOpenClawAvailable(),
169
294
  ]);
170
- return {
171
- available: isDocker || isLocal,
172
- isDocker,
173
- isLocal,
174
- };
295
+ return { available: isDocker || isLocal, isDocker, isLocal };
175
296
  }
176
297
  // ============================================================================
177
298
  // Agent Functions
178
299
  // ============================================================================
179
300
  /**
180
- * Send a message to OpenClaw agent
301
+ * Build agent CLI arguments
181
302
  */
182
- async function sendToOpenClaw(message, options) {
183
- const args = [
303
+ function buildAgentArgs(message) {
304
+ return [
184
305
  'agent',
185
- '--session-id', SESSION_ID,
306
+ '--local',
307
+ '--session-id', exports.CONFIG.sessionId,
186
308
  '--message', message,
187
309
  '--thinking', 'minimal',
188
310
  ];
189
- const timeout = options?.timeout || DEFAULT_TIMEOUT;
190
- const gatewayUrl = options?.gatewayUrl || getGatewayUrl();
191
- const gatewayToken = options?.gatewayToken || getGatewayToken();
192
- return new Promise((resolve, reject) => {
193
- const proc = (0, child_process_1.spawn)('openclaw', args, {
194
- timeout,
195
- stdio: ['ignore', 'pipe', 'pipe'],
196
- env: {
197
- ...process.env,
198
- OPENCLAW_GATEWAY_URL: gatewayUrl,
199
- OPENCLAW_GATEWAY_TOKEN: gatewayToken,
200
- },
201
- });
202
- let stdout = '';
203
- let stderr = '';
204
- proc.stdout?.on('data', (data) => { stdout += data; });
205
- proc.stderr?.on('data', (data) => { stderr += data; });
206
- // Handle abort signal
207
- const abortHandler = () => {
208
- proc.kill('SIGKILL');
209
- reject(new DOMException('The operation was aborted', 'AbortError'));
210
- };
211
- if (options?.signal) {
212
- if (options.signal.aborted) {
213
- proc.kill('SIGKILL');
214
- reject(new DOMException('The operation was aborted', 'AbortError'));
215
- return;
216
- }
217
- options.signal.addEventListener('abort', abortHandler);
218
- }
219
- proc.on('close', (code) => {
220
- options?.signal?.removeEventListener('abort', abortHandler);
221
- if (code === 0) {
222
- resolve(stdout.trim());
223
- }
224
- else if (code === null) {
225
- reject(new DOMException('The operation was aborted', 'AbortError'));
226
- }
227
- else {
228
- reject(new Error(`OpenClaw agent failed: ${stderr || stdout}`));
229
- }
230
- });
231
- proc.on('error', (err) => {
232
- options?.signal?.removeEventListener('abort', abortHandler);
233
- reject(new Error(`Failed to run openclaw: ${err.message}`));
234
- });
235
- });
236
311
  }
237
312
  /**
238
- * Send a message to OpenClaw with streaming output
313
+ * Send message to OpenClaw agent
314
+ */
315
+ async function sendToOpenClaw(message, options) {
316
+ return spawnOpenClaw(buildAgentArgs(message), options);
317
+ }
318
+ /**
319
+ * Send message to OpenClaw with streaming output
239
320
  */
240
321
  async function sendToOpenClawStream(message, onChunk, options) {
241
- const args = [
242
- 'agent',
243
- '--session-id', SESSION_ID,
244
- '--message', message,
245
- '--thinking', 'minimal',
246
- ];
247
- const timeout = options?.timeout || DEFAULT_TIMEOUT;
248
- const gatewayUrl = options?.gatewayUrl || getGatewayUrl();
249
- const gatewayToken = options?.gatewayToken || getGatewayToken();
250
- return new Promise((resolve, reject) => {
251
- const proc = (0, child_process_1.spawn)('openclaw', args, {
252
- timeout,
253
- stdio: ['ignore', 'pipe', 'pipe'],
254
- env: {
255
- ...process.env,
256
- OPENCLAW_GATEWAY_URL: gatewayUrl,
257
- OPENCLAW_GATEWAY_TOKEN: gatewayToken,
258
- },
259
- });
260
- let stdout = '';
261
- let stderr = '';
262
- proc.stdout?.on('data', (data) => {
263
- const chunk = data.toString();
264
- stdout += chunk;
265
- onChunk(chunk);
266
- });
267
- proc.stderr?.on('data', (data) => { stderr += data; });
268
- // Handle abort signal
269
- const abortHandler = () => {
270
- proc.kill('SIGKILL');
271
- reject(new DOMException('The operation was aborted', 'AbortError'));
272
- };
273
- if (options?.signal) {
274
- if (options.signal.aborted) {
275
- proc.kill('SIGKILL');
276
- reject(new DOMException('The operation was aborted', 'AbortError'));
277
- return;
278
- }
279
- options.signal.addEventListener('abort', abortHandler);
280
- }
281
- proc.on('close', (code) => {
282
- options?.signal?.removeEventListener('abort', abortHandler);
283
- if (code === 0) {
284
- resolve(stdout.trim());
285
- }
286
- else if (code === null) {
287
- reject(new DOMException('The operation was aborted', 'AbortError'));
288
- }
289
- else {
290
- reject(new Error(`OpenClaw agent failed: ${stderr || stdout}`));
291
- }
292
- });
293
- proc.on('error', (err) => {
294
- options?.signal?.removeEventListener('abort', abortHandler);
295
- reject(new Error(`Failed to run openclaw: ${err.message}`));
296
- });
297
- });
322
+ return spawnOpenClaw(buildAgentArgs(message), { ...options, onChunk });
298
323
  }
299
324
  // ============================================================================
300
325
  // Cron & Notifications
301
326
  // ============================================================================
302
327
  /**
303
- * Schedule a cron job via OpenClaw
328
+ * Schedule a cron job
304
329
  */
305
330
  async function scheduleCron(name, schedule, prompt) {
306
- const result = await execOpenClaw([
331
+ const spawnConfig = await getSpawnConfig([
307
332
  'cron', 'add',
308
333
  '--name', name,
309
334
  '--schedule', schedule,
310
335
  '--prompt', prompt,
311
- ], { timeout: 10000 });
336
+ ]);
337
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 10000, env: spawnConfig.env });
312
338
  if (!result.success) {
313
339
  throw new Error(`Failed to schedule cron: ${result.stderr || result.stdout}`);
314
340
  }
315
341
  return result.stdout.trim();
316
342
  }
317
343
  /**
318
- * Send a notification via OpenClaw channels
344
+ * Send notification via OpenClaw
319
345
  */
320
346
  async function sendNotification(to, message) {
321
- const result = await execOpenClaw([
347
+ const spawnConfig = await getSpawnConfig([
322
348
  'message', 'send',
323
349
  '--to', to,
324
350
  '--message', message,
325
- ], { timeout: 30000 });
351
+ ]);
352
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
326
353
  if (!result.success) {
327
- throw new Error(`Failed to send message: ${result.stderr || result.stdout}`);
354
+ throw new Error(`Failed to send notification: ${result.stderr || result.stdout}`);
328
355
  }
329
356
  return result.stdout.trim();
330
357
  }
331
358
  /**
332
- * Write to OpenClaw memory
359
+ * Write to OpenClaw memory (local filesystem)
333
360
  */
334
361
  async function writeToMemory(content, filename = 'clawsql-cluster-state.md') {
335
362
  const memoryDir = path.join(os.homedir(), '.openclaw', 'memory');
336
363
  try {
337
364
  await fs.promises.mkdir(memoryDir, { recursive: true });
338
- const filePath = path.join(memoryDir, filename);
339
- await fs.promises.appendFile(filePath, `\n\n---\n\n${content}`);
365
+ await fs.promises.appendFile(path.join(memoryDir, filename), `\n\n---\n\n${content}`);
340
366
  }
341
367
  catch {
342
- // Memory write is optional - silently ignore errors
368
+ // Silently ignore - memory is optional
343
369
  }
344
370
  }
345
371
  // ============================================================================
346
- // Agent Class
372
+ // OpenClawAgent Class
347
373
  // ============================================================================
348
374
  /**
349
- * OpenClaw-backed AI agent
375
+ * OpenClaw-backed AI agent for ClawSQL
350
376
  */
351
377
  class OpenClawAgent {
352
378
  context;
353
- _available = null;
354
- constructor(ctx) {
355
- this.context = ctx;
379
+ available = null;
380
+ constructor(context) {
381
+ this.context = context;
356
382
  }
357
383
  async isAvailable() {
358
- if (this._available === null) {
359
- this._available = await isOpenClawAvailable();
384
+ if (this.available === null) {
385
+ this.available = await isOpenClawAvailable();
360
386
  }
361
- return this._available;
387
+ return this.available;
362
388
  }
363
389
  async process(input) {
364
390
  if (!(await this.isAvailable())) {
365
- throw new Error('OpenClaw gateway is not running. Start it with: openclaw gateway');
391
+ throw new Error('OpenClaw not running. Start with: clawsql /start');
366
392
  }
367
- const contextPrompt = this.buildContextPrompt();
368
- return sendToOpenClaw(`${contextPrompt}\n\nUser query: ${input}`);
393
+ return sendToOpenClaw(`${this.buildContext()}\n\nUser: ${input}`);
369
394
  }
370
- async scheduleHealthCheck(schedule = '0 * * * *') {
371
- return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health using clawsql skill. Report any issues with instances, replication lag, or failover status.');
395
+ async healthCheckCron(schedule = '0 * * * *') {
396
+ return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health. Report issues with instances, replication, or failover.');
372
397
  }
373
398
  async alert(channel, message) {
374
- return sendNotification(channel, `🦞 ClawSQL Alert: ${message}`);
399
+ return sendNotification(channel, `🦞 ClawSQL: ${message}`);
375
400
  }
376
- buildContextPrompt() {
401
+ buildContext() {
377
402
  const { orchestrator, proxysql, failover } = this.context.settings;
378
- return `You are the ClawSQL assistant. You have access to MySQL cluster management tools.
379
-
380
- Current configuration:
381
- - Orchestrator: ${orchestrator.url}
382
- - ProxySQL: ${proxysql.host}:${proxysql.adminPort}
383
- - Auto-failover: ${failover.autoFailoverEnabled ? 'enabled' : 'disabled'}
384
-
385
- Use the clawsql skill commands to answer questions about the MySQL cluster.`;
403
+ return `ClawSQL assistant for MySQL cluster management.
404
+ Orchestrator: ${orchestrator.url}
405
+ ProxySQL: ${proxysql.host}:${proxysql.adminPort}
406
+ Auto-failover: ${failover.autoFailoverEnabled ? 'enabled' : 'disabled'}`;
386
407
  }
387
408
  }
388
409
  exports.OpenClawAgent = OpenClawAgent;
389
- /**
390
- * Create an OpenClaw agent instance
391
- */
392
410
  function createOpenClawAgent(ctx) {
393
411
  return new OpenClawAgent(ctx);
394
412
  }
395
413
  // ============================================================================
396
- // Backwards Compatibility Exports
414
+ // Model Provider Configuration
415
+ // ============================================================================
416
+ exports.SUPPORTED_PROVIDERS = [
417
+ { id: 'anthropic', name: 'Anthropic (Claude)', envKey: 'ANTHROPIC_API_KEY' },
418
+ { id: 'openai', name: 'OpenAI (GPT)', envKey: 'OPENAI_API_KEY' },
419
+ { id: 'xai', name: 'xAI (Grok)', envKey: 'XAI_API_KEY' },
420
+ { id: 'mistral', name: 'Mistral', envKey: 'MISTRAL_API_KEY' },
421
+ { id: 'ollama', name: 'Ollama (Local)', envKey: null },
422
+ { id: 'custom', name: 'Custom Provider', envKey: 'CUSTOM_API_KEY' },
423
+ ];
424
+ /**
425
+ * Get current model provider info
426
+ */
427
+ async function getModelProviderInfo() {
428
+ const spawnConfig = await getSpawnConfig(['config', 'get', 'agents.defaults.model']);
429
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: exports.CONFIG.healthCheckTimeout, env: spawnConfig.env });
430
+ if (!result.success || !result.stdout.trim()) {
431
+ return { provider: null, model: null, configured: false };
432
+ }
433
+ const model = result.stdout.trim();
434
+ const providerMatch = model.match(/^(\w+)\//);
435
+ return {
436
+ provider: providerMatch?.[1] ?? null,
437
+ model,
438
+ configured: !!model,
439
+ };
440
+ }
441
+ /**
442
+ * Configure model provider
443
+ */
444
+ async function configureModelProvider(provider, apiKey, options) {
445
+ const providerInfo = exports.SUPPORTED_PROVIDERS.find(p => p.id === provider);
446
+ if (!providerInfo) {
447
+ return {
448
+ success: false,
449
+ message: `Unknown provider: ${provider}. Supported: ${exports.SUPPORTED_PROVIDERS.map(p => p.id).join(', ')}`,
450
+ };
451
+ }
452
+ const args = ['onboard', '--non-interactive', '--accept-risk'];
453
+ if (provider === 'ollama') {
454
+ args.push('--auth-choice', 'ollama');
455
+ }
456
+ else if (provider === 'custom') {
457
+ args.push('--auth-choice', 'custom-api-key');
458
+ if (apiKey)
459
+ args.push('--custom-api-key', apiKey);
460
+ }
461
+ else {
462
+ args.push('--auth-choice', `${provider}-api-key`);
463
+ if (apiKey)
464
+ args.push(`--${provider}-api-key`, apiKey);
465
+ }
466
+ if (options?.baseUrl)
467
+ args.push('--custom-base-url', options.baseUrl);
468
+ if (options?.modelId)
469
+ args.push('--custom-model-id', options.modelId);
470
+ const spawnConfig = await getSpawnConfig(args);
471
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
472
+ return {
473
+ success: result.success,
474
+ message: result.success
475
+ ? `Configured ${providerInfo.name}`
476
+ : `Failed: ${result.stderr || result.stdout}`,
477
+ };
478
+ }
479
+ /**
480
+ * Test OpenClaw connectivity
481
+ */
482
+ async function testOpenClawConnection(query = 'Hello') {
483
+ const start = Date.now();
484
+ try {
485
+ const response = await sendToOpenClaw(query, { timeout: 30000 });
486
+ return { success: true, response, latencyMs: Date.now() - start };
487
+ }
488
+ catch (error) {
489
+ return {
490
+ success: false,
491
+ error: error instanceof Error ? error.message : String(error),
492
+ latencyMs: Date.now() - start,
493
+ };
494
+ }
495
+ }
496
+ /**
497
+ * Get detailed status for display
498
+ */
499
+ async function getDetailedOpenClawStatus() {
500
+ const status = await getOpenClawStatus();
501
+ const gatewayHealthy = status.available && await isGatewayHealthy();
502
+ const modelInfo = status.available ? await getModelProviderInfo() : { provider: null, model: null, configured: false };
503
+ return {
504
+ available: status.available,
505
+ mode: status.isDocker ? 'docker' : status.isLocal ? 'local' : 'unavailable',
506
+ gatewayHealthy,
507
+ modelInfo,
508
+ controlUI: 'http://localhost:18790',
509
+ gatewayUrl: exports.CONFIG.gatewayUrl,
510
+ };
511
+ }
512
+ // ============================================================================
513
+ // Backwards Compatibility
397
514
  // ============================================================================
398
- /** @deprecated Use isDockerOpenClawAvailable instead */
515
+ /** @deprecated Use isDockerOpenClawAvailable */
399
516
  exports.isDockerOpenClawContainerRunning = isDockerOpenClawAvailable;
400
- /** @deprecated Use isGatewayHealthy instead */
517
+ /** @deprecated Use isGatewayHealthy */
401
518
  exports.isOpenClawGatewayReachable = isGatewayHealthy;
402
- /** @deprecated Use getOpenClawStatus() or individual detection functions */
519
+ /** @deprecated Use isGatewayHealthy() in a loop */
403
520
  async function ensureOpenClawRunning(timeoutSeconds = 30) {
404
- const startTime = Date.now();
405
- while (Date.now() - startTime < timeoutSeconds * 1000) {
521
+ const deadline = Date.now() + timeoutSeconds * 1000;
522
+ while (Date.now() < deadline) {
406
523
  if (await isGatewayHealthy())
407
524
  return true;
408
525
  await new Promise(r => setTimeout(r, 2000));