clawsql 0.2.0 → 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 (63) hide show
  1. package/README.md +25 -5
  2. package/dist/bin/clawsql.d.ts +1 -0
  3. package/dist/bin/clawsql.d.ts.map +1 -1
  4. package/dist/bin/clawsql.js +18 -2
  5. package/dist/bin/clawsql.js.map +1 -1
  6. package/dist/cli/agent/handler.d.ts +13 -31
  7. package/dist/cli/agent/handler.d.ts.map +1 -1
  8. package/dist/cli/agent/handler.js +107 -149
  9. package/dist/cli/agent/handler.js.map +1 -1
  10. package/dist/cli/agent/index.d.ts +2 -1
  11. package/dist/cli/agent/index.d.ts.map +1 -1
  12. package/dist/cli/agent/index.js +7 -1
  13. package/dist/cli/agent/index.js.map +1 -1
  14. package/dist/cli/agent/openclaw-integration.d.ts +83 -25
  15. package/dist/cli/agent/openclaw-integration.d.ts.map +1 -1
  16. package/dist/cli/agent/openclaw-integration.js +305 -194
  17. package/dist/cli/agent/openclaw-integration.js.map +1 -1
  18. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  19. package/dist/cli/commands/cleanup.js +26 -15
  20. package/dist/cli/commands/cleanup.js.map +1 -1
  21. package/dist/cli/commands/doctor.d.ts +0 -4
  22. package/dist/cli/commands/doctor.d.ts.map +1 -1
  23. package/dist/cli/commands/doctor.js +330 -469
  24. package/dist/cli/commands/doctor.js.map +1 -1
  25. package/dist/cli/commands/install.d.ts +13 -0
  26. package/dist/cli/commands/install.d.ts.map +1 -0
  27. package/dist/cli/commands/install.js +286 -0
  28. package/dist/cli/commands/install.js.map +1 -0
  29. package/dist/cli/commands/openclaw.d.ts +9 -0
  30. package/dist/cli/commands/openclaw.d.ts.map +1 -0
  31. package/dist/cli/commands/openclaw.js +236 -0
  32. package/dist/cli/commands/openclaw.js.map +1 -0
  33. package/dist/cli/commands/start.d.ts.map +1 -1
  34. package/dist/cli/commands/start.js +342 -16
  35. package/dist/cli/commands/start.js.map +1 -1
  36. package/dist/cli/commands/status.d.ts.map +1 -1
  37. package/dist/cli/commands/status.js +71 -25
  38. package/dist/cli/commands/status.js.map +1 -1
  39. package/dist/cli/index.d.ts.map +1 -1
  40. package/dist/cli/index.js +4 -0
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/cli/repl.js +1 -1
  43. package/dist/cli/repl.js.map +1 -1
  44. package/dist/cli/utils/ai-config.d.ts +32 -0
  45. package/dist/cli/utils/ai-config.d.ts.map +1 -0
  46. package/dist/cli/utils/ai-config.js +78 -0
  47. package/dist/cli/utils/ai-config.js.map +1 -0
  48. package/dist/cli/utils/command-executor.d.ts.map +1 -1
  49. package/dist/cli/utils/command-executor.js +60 -23
  50. package/dist/cli/utils/command-executor.js.map +1 -1
  51. package/dist/cli/utils/docker-files.d.ts.map +1 -1
  52. package/dist/cli/utils/docker-files.js +2 -0
  53. package/dist/cli/utils/docker-files.js.map +1 -1
  54. package/dist/cli/utils/docker-prereq.d.ts +23 -0
  55. package/dist/cli/utils/docker-prereq.d.ts.map +1 -1
  56. package/dist/cli/utils/docker-prereq.js +70 -1
  57. package/dist/cli/utils/docker-prereq.js.map +1 -1
  58. package/docker/openclaw/entrypoint.sh +102 -0
  59. package/docker/openclaw/openclaw.json +4 -1
  60. package/docker/orchestrator/orchestrator-schema.sql +837 -0
  61. package/docker-compose.yml +22 -14
  62. package/init/metadata.sql +14 -2
  63. 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 = 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
+ const 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: CONFIG.gatewayUrl,
119
+ OPENCLAW_GATEWAY_TOKEN: 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
+ 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 ?? 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=${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() === 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 = 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(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: 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,247 @@ 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
158
277
  */
159
278
  async function isOpenClawAvailable() {
160
279
  return (await isDockerOpenClawAvailable()) || (await isLocalOpenClawAvailable());
161
280
  }
162
281
  /**
163
- * Get detailed OpenClaw status
282
+ * Get OpenClaw status summary
164
283
  */
165
284
  async function getOpenClawStatus() {
166
285
  const [isDocker, isLocal] = await Promise.all([
167
286
  isDockerOpenClawAvailable(),
168
287
  isLocalOpenClawAvailable(),
169
288
  ]);
170
- return {
171
- available: isDocker || isLocal,
172
- isDocker,
173
- isLocal,
174
- };
289
+ return { available: isDocker || isLocal, isDocker, isLocal };
175
290
  }
176
291
  // ============================================================================
177
292
  // Agent Functions
178
293
  // ============================================================================
179
294
  /**
180
- * Send a message to OpenClaw agent
295
+ * Build agent CLI arguments
181
296
  */
182
- async function sendToOpenClaw(message, options) {
183
- const args = [
297
+ function buildAgentArgs(message) {
298
+ return [
184
299
  'agent',
185
- '--session-id', SESSION_ID,
300
+ '--local',
301
+ '--session-id', CONFIG.sessionId,
186
302
  '--message', message,
187
303
  '--thinking', 'minimal',
188
304
  ];
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
305
  }
237
306
  /**
238
- * Send a message to OpenClaw with streaming output
307
+ * Send message to OpenClaw agent
308
+ */
309
+ async function sendToOpenClaw(message, options) {
310
+ return spawnOpenClaw(buildAgentArgs(message), options);
311
+ }
312
+ /**
313
+ * Send message to OpenClaw with streaming output
239
314
  */
240
315
  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
- });
316
+ return spawnOpenClaw(buildAgentArgs(message), { ...options, onChunk });
298
317
  }
299
318
  // ============================================================================
300
319
  // Cron & Notifications
301
320
  // ============================================================================
302
321
  /**
303
- * Schedule a cron job via OpenClaw
322
+ * Schedule a cron job
304
323
  */
305
324
  async function scheduleCron(name, schedule, prompt) {
306
- const result = await execOpenClaw([
325
+ const spawnConfig = await getSpawnConfig([
307
326
  'cron', 'add',
308
327
  '--name', name,
309
328
  '--schedule', schedule,
310
329
  '--prompt', prompt,
311
- ], { timeout: 10000 });
330
+ ]);
331
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 10000, env: spawnConfig.env });
312
332
  if (!result.success) {
313
333
  throw new Error(`Failed to schedule cron: ${result.stderr || result.stdout}`);
314
334
  }
315
335
  return result.stdout.trim();
316
336
  }
317
337
  /**
318
- * Send a notification via OpenClaw channels
338
+ * Send notification via OpenClaw
319
339
  */
320
340
  async function sendNotification(to, message) {
321
- const result = await execOpenClaw([
341
+ const spawnConfig = await getSpawnConfig([
322
342
  'message', 'send',
323
343
  '--to', to,
324
344
  '--message', message,
325
- ], { timeout: 30000 });
345
+ ]);
346
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
326
347
  if (!result.success) {
327
- throw new Error(`Failed to send message: ${result.stderr || result.stdout}`);
348
+ throw new Error(`Failed to send notification: ${result.stderr || result.stdout}`);
328
349
  }
329
350
  return result.stdout.trim();
330
351
  }
331
352
  /**
332
- * Write to OpenClaw memory
353
+ * Write to OpenClaw memory (local filesystem)
333
354
  */
334
355
  async function writeToMemory(content, filename = 'clawsql-cluster-state.md') {
335
356
  const memoryDir = path.join(os.homedir(), '.openclaw', 'memory');
336
357
  try {
337
358
  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}`);
359
+ await fs.promises.appendFile(path.join(memoryDir, filename), `\n\n---\n\n${content}`);
340
360
  }
341
361
  catch {
342
- // Memory write is optional - silently ignore errors
362
+ // Silently ignore - memory is optional
343
363
  }
344
364
  }
345
365
  // ============================================================================
346
- // Agent Class
366
+ // OpenClawAgent Class
347
367
  // ============================================================================
348
368
  /**
349
- * OpenClaw-backed AI agent
369
+ * OpenClaw-backed AI agent for ClawSQL
350
370
  */
351
371
  class OpenClawAgent {
352
372
  context;
353
- _available = null;
354
- constructor(ctx) {
355
- this.context = ctx;
373
+ available = null;
374
+ constructor(context) {
375
+ this.context = context;
356
376
  }
357
377
  async isAvailable() {
358
- if (this._available === null) {
359
- this._available = await isOpenClawAvailable();
378
+ if (this.available === null) {
379
+ this.available = await isOpenClawAvailable();
360
380
  }
361
- return this._available;
381
+ return this.available;
362
382
  }
363
383
  async process(input) {
364
384
  if (!(await this.isAvailable())) {
365
- throw new Error('OpenClaw gateway is not running. Start it with: openclaw gateway');
385
+ throw new Error('OpenClaw not running. Start with: clawsql /start');
366
386
  }
367
- const contextPrompt = this.buildContextPrompt();
368
- return sendToOpenClaw(`${contextPrompt}\n\nUser query: ${input}`);
387
+ return sendToOpenClaw(`${this.buildContext()}\n\nUser: ${input}`);
369
388
  }
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.');
389
+ async healthCheckCron(schedule = '0 * * * *') {
390
+ return scheduleCron('clawsql:health-check', schedule, 'Check MySQL cluster health. Report issues with instances, replication, or failover.');
372
391
  }
373
392
  async alert(channel, message) {
374
- return sendNotification(channel, `🦞 ClawSQL Alert: ${message}`);
393
+ return sendNotification(channel, `🦞 ClawSQL: ${message}`);
375
394
  }
376
- buildContextPrompt() {
395
+ buildContext() {
377
396
  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.`;
397
+ return `ClawSQL assistant for MySQL cluster management.
398
+ Orchestrator: ${orchestrator.url}
399
+ ProxySQL: ${proxysql.host}:${proxysql.adminPort}
400
+ Auto-failover: ${failover.autoFailoverEnabled ? 'enabled' : 'disabled'}`;
386
401
  }
387
402
  }
388
403
  exports.OpenClawAgent = OpenClawAgent;
389
- /**
390
- * Create an OpenClaw agent instance
391
- */
392
404
  function createOpenClawAgent(ctx) {
393
405
  return new OpenClawAgent(ctx);
394
406
  }
395
407
  // ============================================================================
396
- // Backwards Compatibility Exports
408
+ // Model Provider Configuration
409
+ // ============================================================================
410
+ exports.SUPPORTED_PROVIDERS = [
411
+ { id: 'anthropic', name: 'Anthropic (Claude)', envKey: 'ANTHROPIC_API_KEY' },
412
+ { id: 'openai', name: 'OpenAI (GPT)', envKey: 'OPENAI_API_KEY' },
413
+ { id: 'xai', name: 'xAI (Grok)', envKey: 'XAI_API_KEY' },
414
+ { id: 'mistral', name: 'Mistral', envKey: 'MISTRAL_API_KEY' },
415
+ { id: 'ollama', name: 'Ollama (Local)', envKey: null },
416
+ { id: 'custom', name: 'Custom Provider', envKey: 'CUSTOM_API_KEY' },
417
+ ];
418
+ /**
419
+ * Get current model provider info
420
+ */
421
+ async function getModelProviderInfo() {
422
+ const spawnConfig = await getSpawnConfig(['config', 'get', 'agents.defaults.model']);
423
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: CONFIG.healthCheckTimeout, env: spawnConfig.env });
424
+ if (!result.success || !result.stdout.trim()) {
425
+ return { provider: null, model: null, configured: false };
426
+ }
427
+ const model = result.stdout.trim();
428
+ const providerMatch = model.match(/^(\w+)\//);
429
+ return {
430
+ provider: providerMatch?.[1] ?? null,
431
+ model,
432
+ configured: !!model,
433
+ };
434
+ }
435
+ /**
436
+ * Configure model provider
437
+ */
438
+ async function configureModelProvider(provider, apiKey, options) {
439
+ const providerInfo = exports.SUPPORTED_PROVIDERS.find(p => p.id === provider);
440
+ if (!providerInfo) {
441
+ return {
442
+ success: false,
443
+ message: `Unknown provider: ${provider}. Supported: ${exports.SUPPORTED_PROVIDERS.map(p => p.id).join(', ')}`,
444
+ };
445
+ }
446
+ const args = ['onboard', '--non-interactive', '--accept-risk'];
447
+ if (provider === 'ollama') {
448
+ args.push('--auth-choice', 'ollama');
449
+ }
450
+ else if (provider === 'custom') {
451
+ args.push('--auth-choice', 'custom-api-key');
452
+ if (apiKey)
453
+ args.push('--custom-api-key', apiKey);
454
+ }
455
+ else {
456
+ args.push('--auth-choice', `${provider}-api-key`);
457
+ if (apiKey)
458
+ args.push(`--${provider}-api-key`, apiKey);
459
+ }
460
+ if (options?.baseUrl)
461
+ args.push('--custom-base-url', options.baseUrl);
462
+ if (options?.modelId)
463
+ args.push('--custom-model-id', options.modelId);
464
+ const spawnConfig = await getSpawnConfig(args);
465
+ const result = await execCommand(spawnConfig.command, spawnConfig.args, { timeout: 30000, env: spawnConfig.env });
466
+ return {
467
+ success: result.success,
468
+ message: result.success
469
+ ? `Configured ${providerInfo.name}`
470
+ : `Failed: ${result.stderr || result.stdout}`,
471
+ };
472
+ }
473
+ /**
474
+ * Test OpenClaw connectivity
475
+ */
476
+ async function testOpenClawConnection(query = 'Hello') {
477
+ const start = Date.now();
478
+ try {
479
+ const response = await sendToOpenClaw(query, { timeout: 30000 });
480
+ return { success: true, response, latencyMs: Date.now() - start };
481
+ }
482
+ catch (error) {
483
+ return {
484
+ success: false,
485
+ error: error instanceof Error ? error.message : String(error),
486
+ latencyMs: Date.now() - start,
487
+ };
488
+ }
489
+ }
490
+ /**
491
+ * Get detailed status for display
492
+ */
493
+ async function getDetailedOpenClawStatus() {
494
+ const status = await getOpenClawStatus();
495
+ const gatewayHealthy = status.available && await isGatewayHealthy();
496
+ const modelInfo = status.available ? await getModelProviderInfo() : { provider: null, model: null, configured: false };
497
+ return {
498
+ available: status.available,
499
+ mode: status.isDocker ? 'docker' : status.isLocal ? 'local' : 'unavailable',
500
+ gatewayHealthy,
501
+ modelInfo,
502
+ controlUI: 'http://localhost:18790',
503
+ gatewayUrl: CONFIG.gatewayUrl,
504
+ };
505
+ }
506
+ // ============================================================================
507
+ // Backwards Compatibility
397
508
  // ============================================================================
398
- /** @deprecated Use isDockerOpenClawAvailable instead */
509
+ /** @deprecated Use isDockerOpenClawAvailable */
399
510
  exports.isDockerOpenClawContainerRunning = isDockerOpenClawAvailable;
400
- /** @deprecated Use isGatewayHealthy instead */
511
+ /** @deprecated Use isGatewayHealthy */
401
512
  exports.isOpenClawGatewayReachable = isGatewayHealthy;
402
- /** @deprecated Use getOpenClawStatus() or individual detection functions */
513
+ /** @deprecated Use isGatewayHealthy() in a loop */
403
514
  async function ensureOpenClawRunning(timeoutSeconds = 30) {
404
- const startTime = Date.now();
405
- while (Date.now() - startTime < timeoutSeconds * 1000) {
515
+ const deadline = Date.now() + timeoutSeconds * 1000;
516
+ while (Date.now() < deadline) {
406
517
  if (await isGatewayHealthy())
407
518
  return true;
408
519
  await new Promise(r => setTimeout(r, 2000));