clawfix 0.2.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/bin/clawfix.js +757 -123
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -24,7 +24,7 @@ That's it. ClawFix scans your OpenClaw setup, finds issues, and generates fix sc
24
24
  - All secrets, tokens, and API keys are **automatically redacted** before leaving your machine
25
25
  - Diagnostic data is only sent with your **explicit consent**
26
26
  - No telemetry, no tracking, no account required
27
- - [Source code is open](https://github.com/arcaboteth/clawfix) — verify it yourself
27
+ - [Source code is open](https://github.com/arcabotai/clawfix) — verify it yourself
28
28
 
29
29
  ## Options
30
30
 
@@ -51,8 +51,8 @@ curl -sSL clawfix.dev/fix | bash
51
51
  ## Links
52
52
 
53
53
  - **Website:** [clawfix.dev](https://clawfix.dev)
54
- - **GitHub:** [arcaboteth/clawfix](https://github.com/arcaboteth/clawfix)
55
- - **Issues:** [github.com/arcaboteth/clawfix/issues](https://github.com/arcaboteth/clawfix/issues)
54
+ - **GitHub:** [arcabotai/clawfix](https://github.com/arcabotai/clawfix)
55
+ - **Issues:** [github.com/arcabotai/clawfix/issues](https://github.com/arcabotai/clawfix/issues)
56
56
  - **Made by:** [Arca](https://arcabot.ai) (arcabot.eth)
57
57
 
58
58
  ## License
package/bin/clawfix.js CHANGED
@@ -3,19 +3,21 @@
3
3
  /**
4
4
  * ClawFix CLI — AI-powered OpenClaw diagnostic & repair
5
5
  * https://clawfix.dev
6
- *
7
- * Usage: npx clawfix
6
+ *
7
+ * Usage: npx clawfix (interactive TUI)
8
+ * npx clawfix --scan (one-shot scan, legacy mode)
8
9
  */
9
10
 
10
11
  import { readFile, access, readdir, stat } from 'node:fs/promises';
11
12
  import { execSync } from 'node:child_process';
12
13
  import { homedir, platform, arch, release, hostname } from 'node:os';
13
14
  import { join } from 'node:path';
14
- import { createHash } from 'node:crypto';
15
+ import { createHash, randomUUID } from 'node:crypto';
16
+ import { createInterface } from 'node:readline';
15
17
 
16
18
  // --- Config ---
17
19
  const API_URL = process.env.CLAWFIX_API || 'https://clawfix.dev';
18
- const VERSION = '0.2.0';
20
+ const VERSION = '0.6.0';
19
21
 
20
22
  // --- Flags ---
21
23
  const args = process.argv.slice(2);
@@ -23,6 +25,7 @@ const DRY_RUN = args.includes('--dry-run') || args.includes('-n');
23
25
  const SHOW_DATA = args.includes('--show-data') || args.includes('-d');
24
26
  const AUTO_SEND = process.env.CLAWFIX_AUTO === '1' || args.includes('--yes') || args.includes('-y');
25
27
  const SHOW_HELP = args.includes('--help') || args.includes('-h');
28
+ const ONE_SHOT = args.includes('--scan') || args.includes('--no-interactive') || DRY_RUN;
26
29
 
27
30
  // --- Colors ---
28
31
  const c = {
@@ -33,6 +36,7 @@ const c = {
33
36
  cyan: s => `\x1b[36m${s}\x1b[0m`,
34
37
  bold: s => `\x1b[1m${s}\x1b[0m`,
35
38
  dim: s => `\x1b[2m${s}\x1b[0m`,
39
+ magenta: s => `\x1b[35m${s}\x1b[0m`,
36
40
  };
37
41
 
38
42
  // --- Helpers ---
@@ -52,10 +56,9 @@ function hashStr(s) {
52
56
  return createHash('sha256').update(s).digest('hex').slice(0, 8);
53
57
  }
54
58
 
55
- // Redact secrets from config
56
59
  function sanitizeConfig(config) {
57
60
  if (!config || typeof config !== 'object') return config;
58
-
61
+
59
62
  const redact = (obj) => {
60
63
  if (typeof obj === 'string') {
61
64
  if (obj.length > 20 && /^(sk-|xai-|eyJ|ghp_|gho_|npm_|m0-|AIza|ntn_)/.test(obj)) return '***REDACTED***';
@@ -69,7 +72,7 @@ function sanitizeConfig(config) {
69
72
  if (/key|token|secret|password|jwt|apikey|accesstoken/i.test(k)) {
70
73
  result[k] = '***REDACTED***';
71
74
  } else if (k === 'env') {
72
- continue; // Skip env block entirely
75
+ continue;
73
76
  } else {
74
77
  result[k] = redact(v);
75
78
  }
@@ -78,74 +81,38 @@ function sanitizeConfig(config) {
78
81
  }
79
82
  return obj;
80
83
  };
81
-
84
+
82
85
  return redact(config);
83
86
  }
84
87
 
85
- // --- Main ---
86
- async function main() {
87
- if (SHOW_HELP) {
88
- console.log(`
89
- 🦞 ClawFix v${VERSION} AI-Powered OpenClaw Diagnostic
90
-
91
- Usage: npx clawfix [options]
92
-
93
- Options:
94
- --dry-run, -n Scan locally only — shows what would be collected, sends nothing
95
- --show-data, -d Display the full diagnostic payload before asking to send
96
- --yes, -y Skip confirmation prompt and send automatically
97
- --help, -h Show this help message
98
-
99
- Environment:
100
- CLAWFIX_API Override API URL (default: https://clawfix.dev)
101
- CLAWFIX_AUTO=1 Same as --yes
102
-
103
- Security:
104
- • All API keys, tokens, and passwords are automatically redacted
105
- • Your hostname is SHA-256 hashed (only first 8 chars sent)
106
- • No file contents are read (only existence checks)
107
- • Nothing is sent without your explicit approval (unless --yes)
108
- • Source code: https://github.com/arcaboteth/clawfix
109
-
110
- Examples:
111
- npx clawfix # Interactive scan + optional AI analysis
112
- npx clawfix --dry-run # See what data would be collected (sends nothing)
113
- npx clawfix --show-data # Show full payload before asking to send
114
- npx clawfix --yes # Auto-send for CI/scripting
115
- `);
116
- return;
117
- }
118
-
119
- console.log('');
120
- console.log(c.cyan(`🦞 ClawFix v${VERSION} — AI-Powered OpenClaw Diagnostic`));
121
- if (DRY_RUN) console.log(c.yellow(' 🔍 DRY RUN MODE — nothing will be sent'));
122
- console.log(c.cyan('━'.repeat(50)));
123
- console.log('');
88
+ // ============================================================
89
+ // collectDiagnostics() — reusable scan, returns { diagnostic, issues, summary }
90
+ // ============================================================
91
+ async function collectDiagnostics({ quiet = false } = {}) {
92
+ const log = quiet ? () => {} : (...a) => console.log(...a);
124
93
 
125
94
  // --- Detect OpenClaw ---
126
95
  const home = homedir();
127
96
  const openclawDir = await exists(join(home, '.openclaw')) ? join(home, '.openclaw') :
128
97
  await exists(join(home, '.config', 'openclaw')) ? join(home, '.config', 'openclaw') : null;
129
-
130
- const openclawBin = run('which openclaw') ||
98
+
99
+ const openclawBin = run('which openclaw') ||
131
100
  (await exists('/opt/homebrew/bin/openclaw') ? '/opt/homebrew/bin/openclaw' : '') ||
132
101
  (await exists('/usr/local/bin/openclaw') ? '/usr/local/bin/openclaw' : '');
133
102
 
134
103
  const configPath = openclawDir ? join(openclawDir, 'openclaw.json') : null;
135
104
 
136
105
  if (!openclawBin && !openclawDir) {
137
- console.log(c.red('OpenClaw not found on this system.'));
138
- console.log('Make sure OpenClaw is installed: https://openclaw.ai');
139
- process.exit(1);
106
+ return { error: 'OpenClaw not found on this system.' };
140
107
  }
141
108
 
142
- console.log(c.green('✅ OpenClaw found'));
143
- if (openclawBin) console.log(` Binary: ${openclawBin}`);
144
- if (openclawDir) console.log(` Config: ${openclawDir}`);
109
+ log(c.green('✅ OpenClaw found'));
110
+ if (openclawBin) log(` Binary: ${openclawBin}`);
111
+ if (openclawDir) log(` Config: ${openclawDir}`);
145
112
 
146
113
  // --- System Info ---
147
- console.log('');
148
- console.log(c.blue('📋 Collecting system information...'));
114
+ log('');
115
+ log(c.blue('📋 Collecting system information...'));
149
116
 
150
117
  const osName = platform();
151
118
  const osVersion = release();
@@ -159,13 +126,13 @@ Examples:
159
126
  ocVersion = run(`"${openclawBin}" --version`);
160
127
  }
161
128
 
162
- console.log(` OS: ${osName} ${osVersion} (${osArch})`);
163
- console.log(` Node: ${nodeVersion}`);
164
- console.log(` OpenClaw: ${ocVersion || 'not found'}`);
129
+ log(` OS: ${osName} ${osVersion} (${osArch})`);
130
+ log(` Node: ${nodeVersion}`);
131
+ log(` OpenClaw: ${ocVersion || 'not found'}`);
165
132
 
166
133
  // --- Read Config ---
167
- console.log('');
168
- console.log(c.blue('🔒 Reading config (secrets will be redacted)...'));
134
+ log('');
135
+ log(c.blue('🔒 Reading config (secrets will be redacted)...'));
169
136
 
170
137
  let config = null;
171
138
  let sanitizedConfig = {};
@@ -173,14 +140,14 @@ Examples:
173
140
  if (configPath && await exists(configPath)) {
174
141
  config = await readJson(configPath);
175
142
  sanitizedConfig = sanitizeConfig(config) || {};
176
- console.log(c.green(' ✅ Config read and sanitized'));
143
+ log(c.green(' ✅ Config read and sanitized'));
177
144
  } else {
178
- console.log(c.yellow(' ⚠️ No config file found'));
145
+ log(c.yellow(' ⚠️ No config file found'));
179
146
  }
180
147
 
181
148
  // --- Gateway Status ---
182
- console.log('');
183
- console.log(c.blue('🔌 Checking gateway status...'));
149
+ log('');
150
+ log(c.blue('🔌 Checking gateway status...'));
184
151
 
185
152
  let gatewayStatus = 'unknown';
186
153
  if (openclawBin) {
@@ -190,55 +157,125 @@ Examples:
190
157
  const gatewayPort = config?.gateway?.port || 18789;
191
158
  const gatewayPid = run('pgrep -f "openclaw.*gateway"') || '';
192
159
 
193
- // Extract the actual status line, not config warnings
194
160
  const statusLine = gatewayStatus.split('\n').find(l => /runtime:|listening|running|stopped|not running/i.test(l))
195
161
  || gatewayStatus.split('\n')[0];
196
- console.log(` Status: ${statusLine.trim()}`);
197
- if (gatewayPid) console.log(` PID: ${gatewayPid}`);
198
- console.log(` Port: ${gatewayPort}`);
162
+ log(` Status: ${statusLine.trim()}`);
163
+ if (gatewayPid) log(` PID: ${gatewayPid}`);
164
+ log(` Port: ${gatewayPort}`);
199
165
 
200
166
  // --- Logs ---
201
- console.log('');
202
- console.log(c.blue('📜 Reading recent logs...'));
167
+ log('');
168
+ log(c.blue('📜 Reading recent logs...'));
203
169
 
204
170
  let errorLogs = '';
205
171
  let stderrLogs = '';
172
+ let gatewayLogTail = '';
173
+ let errLogSizeMB = 0;
174
+ let logSizeMB = 0;
206
175
 
207
176
  const logPath = openclawDir ? join(openclawDir, 'logs', 'gateway.log') : null;
208
177
  const errLogPath = openclawDir ? join(openclawDir, 'logs', 'gateway.err.log') : null;
209
178
 
210
179
  if (logPath && await exists(logPath)) {
211
180
  try {
212
- const logContent = await readFile(logPath, 'utf8');
213
- const lines = logContent.split('\n');
181
+ const logStat = await stat(logPath);
182
+ logSizeMB = Math.round(logStat.size / 1024 / 1024);
183
+ const tailContent = run(`tail -500 "${logPath}" 2>/dev/null`);
184
+ const lines = tailContent.split('\n');
214
185
  errorLogs = lines
215
186
  .filter(l => /error|warn|fail|crash|EADDRINUSE|EACCES/i.test(l))
216
187
  .slice(-30)
217
188
  .join('\n');
218
- console.log(c.green(` ✅ Gateway log found (${lines.length} lines)`));
189
+ gatewayLogTail = lines
190
+ .filter(l => /signal SIGTERM|listening.*PID|config change detected.*reload|update available/i.test(l))
191
+ .slice(-20)
192
+ .join('\n');
193
+ log(c.green(` ✅ Gateway log found (${logSizeMB}MB, read last 500 lines)`));
219
194
  } catch {}
220
195
  }
221
196
 
222
197
  if (errLogPath && await exists(errLogPath)) {
223
198
  try {
224
- stderrLogs = (await readFile(errLogPath, 'utf8')).split('\n').slice(-50).join('\n');
225
- console.log(c.green(' ✅ Error log found'));
199
+ const errStat = await stat(errLogPath);
200
+ errLogSizeMB = Math.round(errStat.size / 1024 / 1024);
201
+ stderrLogs = run(`tail -200 "${errLogPath}" 2>/dev/null`);
202
+ const icon = errLogSizeMB > 50 ? c.yellow('⚠️') : c.green('✅');
203
+ log(` ${icon} Error log found (${errLogSizeMB}MB${errLogSizeMB > 50 ? ' — OVERSIZED!' : ''})`);
226
204
  } catch {}
227
205
  }
228
206
 
207
+ // --- Service Health ---
208
+ log('');
209
+ log(c.blue('🔧 Checking service health...'));
210
+
211
+ let serviceHealth = {};
212
+ const isMac = osName === 'darwin';
213
+ const isLinux = osName === 'linux';
214
+
215
+ if (isMac) {
216
+ const uid = run('id -u');
217
+ const launchdInfo = run(`launchctl print gui/${uid}/ai.openclaw.gateway 2>/dev/null`);
218
+ if (launchdInfo) {
219
+ const runsMatch = launchdInfo.match(/runs = (\d+)/);
220
+ const pidMatch = launchdInfo.match(/pid = (\d+)/);
221
+ const stateMatch = launchdInfo.match(/state = (running|waiting|not running)/);
222
+ const exitCodeMatch = launchdInfo.match(/last exit code = (\d+)/);
223
+ serviceHealth = {
224
+ manager: 'launchd',
225
+ runs: runsMatch ? parseInt(runsMatch[1]) : 0,
226
+ pid: pidMatch ? parseInt(pidMatch[1]) : 0,
227
+ state: stateMatch ? stateMatch[1] : 'unknown',
228
+ lastExitCode: exitCodeMatch ? parseInt(exitCodeMatch[1]) : null,
229
+ };
230
+ if (serviceHealth.pid) {
231
+ const elapsed = run(`ps -p ${serviceHealth.pid} -o etime= 2>/dev/null`).trim();
232
+ serviceHealth.uptimeStr = elapsed;
233
+ const parts = elapsed.replace(/-/g, ':').split(':').reverse().map(Number);
234
+ serviceHealth.uptimeSeconds = (parts[0] || 0) + (parts[1] || 0) * 60 + (parts[2] || 0) * 3600 + (parts[3] || 0) * 86400;
235
+ }
236
+ const runsIcon = serviceHealth.runs > 2 ? c.yellow('⚠️') : c.green('✅');
237
+ log(` ${runsIcon} LaunchAgent: ${serviceHealth.state} (${serviceHealth.runs} run(s), PID ${serviceHealth.pid || 'none'})`);
238
+ if (serviceHealth.uptimeStr) log(` Uptime: ${serviceHealth.uptimeStr}`);
239
+ if (serviceHealth.runs > 2) log(c.yellow(` ⚠️ Multiple restarts detected — possible crash loop`));
240
+ } else {
241
+ log(c.dim(' LaunchAgent not found'));
242
+ }
243
+ } else if (isLinux) {
244
+ const systemdInfo = run('systemctl show openclaw-gateway --property=NRestarts,ActiveState,SubState,ExecMainPID,ExecMainStartTimestamp 2>/dev/null');
245
+ if (systemdInfo) {
246
+ const props = {};
247
+ systemdInfo.split('\n').forEach(l => {
248
+ const [k, v] = l.split('=', 2);
249
+ if (k && v) props[k.trim()] = v.trim();
250
+ });
251
+ serviceHealth = {
252
+ manager: 'systemd',
253
+ nRestarts: parseInt(props.NRestarts) || 0,
254
+ state: props.ActiveState || 'unknown',
255
+ subState: props.SubState || 'unknown',
256
+ pid: parseInt(props.ExecMainPID) || 0,
257
+ };
258
+ log(` systemd: ${serviceHealth.state}/${serviceHealth.subState} (${serviceHealth.nRestarts} restart(s))`);
259
+ } else {
260
+ log(c.dim(' systemd service not found'));
261
+ }
262
+ } else {
263
+ log(c.dim(' Service manager detection not available on this OS'));
264
+ }
265
+
229
266
  // --- Plugins ---
230
- console.log('');
231
- console.log(c.blue('🔌 Checking plugins...'));
267
+ log('');
268
+ log(c.blue('🔌 Checking plugins...'));
232
269
 
233
270
  const plugins = config?.plugins?.entries || {};
234
271
  for (const [name, cfg] of Object.entries(plugins)) {
235
272
  const icon = cfg.enabled === false ? '❌' : '✅';
236
- console.log(` ${icon} ${name}`);
273
+ log(` ${icon} ${name}`);
237
274
  }
238
275
 
239
276
  // --- Workspace ---
240
- console.log('');
241
- console.log(c.blue('📁 Checking workspace...'));
277
+ log('');
278
+ log(c.blue('📁 Checking workspace...'));
242
279
 
243
280
  const workspaceDir = config?.agents?.defaults?.workspace || '';
244
281
  let mdFiles = 0;
@@ -263,25 +300,28 @@ Examples:
263
300
  } catch {}
264
301
  }
265
302
 
266
- console.log(` Path: ${workspaceDir}`);
267
- console.log(` Files: ${mdFiles} .md files`);
268
- console.log(` Memory: ${memoryFiles} daily notes`);
269
- console.log(` SOUL.md: ${hasSoul}`);
270
- console.log(` AGENTS.md: ${hasAgents}`);
303
+ log(` Path: ${workspaceDir}`);
304
+ log(` Files: ${mdFiles} .md files`);
305
+ log(` Memory: ${memoryFiles} daily notes`);
306
+ log(` SOUL.md: ${hasSoul}`);
307
+ log(` AGENTS.md: ${hasAgents}`);
271
308
  }
272
309
 
273
310
  // --- Check Ports ---
274
- console.log('');
275
- console.log(c.blue('🔗 Checking port availability...'));
311
+ log('');
312
+ log(c.blue('🔗 Checking port availability...'));
276
313
 
314
+ const portResults = {};
277
315
  const checkPort = (port, name) => {
278
316
  const inUse = run(`lsof -i :${port} 2>/dev/null | grep LISTEN`) ||
279
317
  run(`ss -tlnp 2>/dev/null | grep :${port}`);
280
318
  if (inUse) {
281
- console.log(c.yellow(` ⚠️ Port ${port} (${name}) — IN USE`));
319
+ log(c.yellow(` ⚠️ Port ${port} (${name}) — IN USE`));
320
+ portResults[port] = true;
282
321
  return true;
283
322
  } else {
284
- console.log(c.green(` ✅ Port ${port} (${name}) — available`));
323
+ log(c.green(` ✅ Port ${port} (${name}) — available`));
324
+ portResults[port] = false;
285
325
  return false;
286
326
  }
287
327
  };
@@ -291,15 +331,8 @@ Examples:
291
331
  checkPort(18791, 'browser control');
292
332
 
293
333
  // --- Local Issue Detection ---
294
- console.log('');
295
- console.log(c.cyan('━'.repeat(50)));
296
- console.log(c.bold('📊 Diagnostic Summary'));
297
- console.log(c.cyan('━'.repeat(50)));
298
- console.log('');
299
-
300
334
  const issues = [];
301
335
 
302
- // Check actual gateway status — ignore config warnings in output
303
336
  const gatewayRunning = /running.*pid|state active|listening/i.test(gatewayStatus);
304
337
  const gatewayFailed = /not running|failed to start|stopped|inactive/i.test(gatewayStatus);
305
338
  if (gatewayFailed || (!gatewayRunning && !/warning/i.test(gatewayStatus))) {
@@ -308,6 +341,40 @@ Examples:
308
341
  if (/EADDRINUSE/i.test(errorLogs)) {
309
342
  issues.push({ severity: 'critical', text: 'Port conflict detected' });
310
343
  }
344
+
345
+ const sigtermCount = (gatewayLogTail.match(/signal SIGTERM/gi) || []).length;
346
+ const restartCount = (gatewayLogTail.match(/listening.*PID/gi) || []).length;
347
+ if (config?.update?.auto?.enabled === true && (sigtermCount >= 2 || restartCount >= 3)) {
348
+ issues.push({ severity: 'critical', text: 'Auto-update causing gateway restart loop' });
349
+ } else if (config?.update?.auto?.enabled === true) {
350
+ issues.push({ severity: 'medium', text: 'Auto-update enabled (risk of restart loops)' });
351
+ }
352
+
353
+ const reloadCount = (gatewayLogTail.match(/config change detected.*evaluating reload/gi) || []).length;
354
+ if (reloadCount >= 3) {
355
+ issues.push({ severity: 'high', text: `Config reload cascade detected (${reloadCount} reloads in recent logs)` });
356
+ }
357
+
358
+ if (serviceHealth.runs > 2 && (serviceHealth.uptimeSeconds || 0) < 300) {
359
+ issues.push({ severity: 'critical', text: `Gateway crash loop — ${serviceHealth.runs} restarts, only ${serviceHealth.uptimeStr} uptime` });
360
+ } else if ((serviceHealth.nRestarts || 0) > 0) {
361
+ issues.push({ severity: 'high', text: `Gateway has restarted ${serviceHealth.nRestarts} time(s) (systemd)` });
362
+ }
363
+
364
+ const handshakeSpam = (stderrLogs.match(/invalid handshake.*chrome-extension|closed before connect.*chrome-extension/gi) || []).length;
365
+ if (handshakeSpam >= 5) {
366
+ issues.push({ severity: 'medium', text: 'Browser Relay extension spamming invalid handshakes' });
367
+ }
368
+
369
+ if (errLogSizeMB > 50) {
370
+ issues.push({ severity: 'medium', text: `Error log is ${errLogSizeMB}MB (should be <50MB)` });
371
+ }
372
+
373
+ const matrixTimeouts = (stderrLogs.match(/ESOCKETTIMEDOUT/gi) || []).length;
374
+ if (matrixTimeouts >= 3) {
375
+ issues.push({ severity: 'low', text: 'Matrix sync timeouts spamming error log' });
376
+ }
377
+
311
378
  if (config?.plugins?.entries?.['openclaw-mem0']?.config?.enableGraph === true) {
312
379
  issues.push({ severity: 'high', text: 'Mem0 enableGraph requires Pro plan (will silently fail)' });
313
380
  }
@@ -327,23 +394,6 @@ Examples:
327
394
  issues.push({ severity: 'low', text: 'No memory files found' });
328
395
  }
329
396
 
330
- if (issues.length === 0) {
331
- console.log(c.green('✅ No issues detected! Your OpenClaw looks healthy.'));
332
- } else {
333
- console.log(c.red(`Found ${issues.length} issue(s):`));
334
- console.log('');
335
- for (const issue of issues) {
336
- const icon = issue.severity === 'critical' ? c.red('❌') :
337
- issue.severity === 'high' ? c.red('❌') :
338
- c.yellow('⚠️');
339
- console.log(` ${icon} [${issue.severity.toUpperCase()}] ${issue.text}`);
340
- }
341
- }
342
-
343
- console.log('');
344
- console.log(c.cyan('━'.repeat(50)));
345
- console.log('');
346
-
347
397
  // --- Build Payload ---
348
398
  const diagnostic = {
349
399
  version: VERSION,
@@ -368,7 +418,11 @@ Examples:
368
418
  logs: {
369
419
  errors: errorLogs,
370
420
  stderr: stderrLogs,
421
+ gatewayLog: gatewayLogTail,
422
+ errLogSizeMB,
423
+ logSizeMB,
371
424
  },
425
+ service: serviceHealth,
372
426
  workspace: {
373
427
  path: workspaceDir || 'unknown',
374
428
  mdFiles,
@@ -381,6 +435,72 @@ Examples:
381
435
  },
382
436
  };
383
437
 
438
+ // Build summary for TUI display
439
+ const gatewayIcon = gatewayRunning ? c.green('✓') : c.red('✗');
440
+ const gatewayLabel = gatewayRunning
441
+ ? `running (pid ${gatewayPid || '?'}, port ${gatewayPort})`
442
+ : 'not running';
443
+ const configIcon = config ? c.green('✓') : c.yellow('⚠');
444
+ const configLabel = config ? 'loaded' : 'not found';
445
+ const issueIcon = issues.length === 0 ? c.green('✓') : c.yellow('⚠');
446
+ const issueLabel = issues.length === 0 ? 'No issues' : `${issues.length} issue(s) detected`;
447
+
448
+ const summary = {
449
+ gateway: { icon: gatewayIcon, label: gatewayLabel },
450
+ config: { icon: configIcon, label: configLabel },
451
+ issues: { icon: issueIcon, label: issueLabel },
452
+ node: nodeVersion,
453
+ os: `${osName === 'darwin' ? 'macOS' : osName} ${osVersion}`,
454
+ ocVersion: ocVersion || 'unknown',
455
+ };
456
+
457
+ return { diagnostic, issues, summary };
458
+ }
459
+
460
+ // ============================================================
461
+ // One-shot mode (legacy: --scan, --dry-run, --no-interactive)
462
+ // ============================================================
463
+ async function runOneShotMode() {
464
+ console.log('');
465
+ console.log(c.cyan(`🦞 ClawFix v${VERSION} — AI-Powered OpenClaw Diagnostic`));
466
+ if (DRY_RUN) console.log(c.yellow(' 🔍 DRY RUN MODE — nothing will be sent'));
467
+ console.log(c.cyan('━'.repeat(50)));
468
+ console.log('');
469
+
470
+ const result = await collectDiagnostics();
471
+
472
+ if (result.error) {
473
+ console.log(c.red(`❌ ${result.error}`));
474
+ console.log('Make sure OpenClaw is installed: https://openclaw.ai');
475
+ process.exit(1);
476
+ }
477
+
478
+ const { diagnostic, issues } = result;
479
+
480
+ // --- Display issues ---
481
+ console.log('');
482
+ console.log(c.cyan('━'.repeat(50)));
483
+ console.log(c.bold('📊 Diagnostic Summary'));
484
+ console.log(c.cyan('━'.repeat(50)));
485
+ console.log('');
486
+
487
+ if (issues.length === 0) {
488
+ console.log(c.green('✅ No issues detected! Your OpenClaw looks healthy.'));
489
+ } else {
490
+ console.log(c.red(`Found ${issues.length} issue(s):`));
491
+ console.log('');
492
+ for (const issue of issues) {
493
+ const icon = issue.severity === 'critical' ? c.red('❌') :
494
+ issue.severity === 'high' ? c.red('❌') :
495
+ c.yellow('⚠️');
496
+ console.log(` ${icon} [${issue.severity.toUpperCase()}] ${issue.text}`);
497
+ }
498
+ }
499
+
500
+ console.log('');
501
+ console.log(c.cyan('━'.repeat(50)));
502
+ console.log('');
503
+
384
504
  // --- Show collected data ---
385
505
  if (DRY_RUN || SHOW_DATA) {
386
506
  console.log('');
@@ -398,18 +518,17 @@ Examples:
398
518
  console.log(c.cyan(' npx clawfix'));
399
519
  console.log('');
400
520
  console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
401
- console.log(c.cyan(' https://clawfix.dev | https://x.com/arcaboteth'));
521
+ console.log(c.cyan(' https://clawfix.dev | https://x.com/arcabotai'));
402
522
  console.log('');
403
523
  return;
404
524
  }
405
525
 
406
- // --- Send for AI analysis ---
407
526
  if (issues.length === 0) {
408
527
  console.log(c.green('Your OpenClaw is looking good! No fixes needed.'));
409
528
  console.log(`If you're still having issues, run with --show-data to see what would be collected.`);
410
529
  console.log('');
411
530
  console.log(c.cyan(`🦞 ClawFix — made by Arca (arcabot.eth)`));
412
- console.log(c.cyan(` https://clawfix.dev | https://x.com/arcaboteth`));
531
+ console.log(c.cyan(` https://clawfix.dev | https://x.com/arcabotai`));
413
532
  console.log('');
414
533
  return;
415
534
  }
@@ -423,8 +542,7 @@ Examples:
423
542
 
424
543
  let shouldSend = AUTO_SEND;
425
544
  if (!shouldSend) {
426
- const readline = await import('node:readline');
427
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
545
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
428
546
  const answer = await new Promise(resolve => {
429
547
  rl.question('Send diagnostic for AI analysis? [y/N] ', resolve);
430
548
  });
@@ -461,7 +579,6 @@ Examples:
461
579
  console.log(c.green(`✅ Diagnosis complete! Found ${result.issuesFound} issue(s).`));
462
580
  console.log('');
463
581
 
464
- // Show known issues
465
582
  if (result.knownIssues) {
466
583
  for (const issue of result.knownIssues) {
467
584
  console.log(` ${issue.severity.toUpperCase()} — ${issue.title}: ${issue.description}`);
@@ -473,7 +590,6 @@ Examples:
473
590
  console.log(result.analysis || 'Pattern matching only (no AI configured)');
474
591
  console.log('');
475
592
 
476
- // Save fix script
477
593
  if (result.fixScript) {
478
594
  const { writeFile } = await import('node:fs/promises');
479
595
  const fixPath = `/tmp/clawfix-${fixId}.sh`;
@@ -499,8 +615,526 @@ Examples:
499
615
 
500
616
  console.log('');
501
617
  console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
502
- console.log(c.cyan(' https://clawfix.dev | https://x.com/arcaboteth'));
618
+ console.log(c.cyan(' https://clawfix.dev | https://x.com/arcabotai'));
619
+ console.log('');
620
+ }
621
+
622
+ // ============================================================
623
+ // Interactive TUI mode (default)
624
+ // ============================================================
625
+ async function runInteractiveMode() {
626
+ const conversationId = randomUUID();
627
+ let diagnosticId = null;
628
+ let issues = [];
629
+ let diagnostic = null;
630
+ let summary = null;
631
+ let serverIssues = null; // issues returned from server after /api/diagnose
632
+
633
+ // --- Clear screen and show header ---
634
+ process.stdout.write('\x1b[2J\x1b[H');
635
+
636
+ console.log('');
637
+ console.log(c.cyan(`🦞 ClawFix v${VERSION}`));
638
+ console.log(c.cyan('━'.repeat(48)));
639
+ console.log('');
640
+ console.log(c.dim('Scanning your OpenClaw installation...'));
641
+ console.log('');
642
+
643
+ // --- Auto-scan on startup ---
644
+ const scanResult = await collectDiagnostics({ quiet: true });
645
+
646
+ if (scanResult.error) {
647
+ console.log(c.red(`❌ ${scanResult.error}`));
648
+ console.log('Make sure OpenClaw is installed: https://openclaw.ai');
649
+ process.exit(1);
650
+ }
651
+
652
+ diagnostic = scanResult.diagnostic;
653
+ issues = scanResult.issues;
654
+ summary = scanResult.summary;
655
+
656
+ // --- Send diagnostic to server for AI context ---
657
+ try {
658
+ const resp = await fetch(`${API_URL}/api/diagnose`, {
659
+ method: 'POST',
660
+ headers: { 'Content-Type': 'application/json' },
661
+ body: JSON.stringify(diagnostic),
662
+ });
663
+ if (resp.ok) {
664
+ const data = await resp.json();
665
+ diagnosticId = data.fixId;
666
+ serverIssues = data.knownIssues || [];
667
+ }
668
+ } catch {
669
+ // Server unavailable — continue in local-only mode
670
+ }
671
+
672
+ // --- Render TUI ---
673
+ renderStatus(summary, issues, serverIssues);
674
+
675
+ // --- Start interactive prompt ---
676
+ const rl = createInterface({
677
+ input: process.stdin,
678
+ output: process.stdout,
679
+ prompt: `${c.cyan('clawfix')}${c.dim('>')} `,
680
+ terminal: true,
681
+ });
682
+
683
+ rl.prompt();
684
+
685
+ rl.on('line', async (line) => {
686
+ const input = line.trim();
687
+
688
+ if (!input) {
689
+ // Empty enter → show issues summary
690
+ renderIssues(issues, serverIssues);
691
+ rl.prompt();
692
+ return;
693
+ }
694
+
695
+ // --- Built-in commands ---
696
+ if (/^(exit|quit|q)$/i.test(input)) {
697
+ console.log('');
698
+ console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
699
+ console.log(c.cyan(' https://clawfix.dev'));
700
+ console.log('');
701
+ process.exit(0);
702
+ }
703
+
704
+ if (/^(help|\?)$/i.test(input)) {
705
+ renderHelp();
706
+ rl.prompt();
707
+ return;
708
+ }
709
+
710
+ if (/^(scan|rescan)$/i.test(input)) {
711
+ console.log('');
712
+ console.log(c.dim('Rescanning...'));
713
+ console.log('');
714
+ const result = await collectDiagnostics({ quiet: true });
715
+ if (!result.error) {
716
+ diagnostic = result.diagnostic;
717
+ issues = result.issues;
718
+ summary = result.summary;
719
+
720
+ // Re-send to server
721
+ try {
722
+ const resp = await fetch(`${API_URL}/api/diagnose`, {
723
+ method: 'POST',
724
+ headers: { 'Content-Type': 'application/json' },
725
+ body: JSON.stringify(diagnostic),
726
+ });
727
+ if (resp.ok) {
728
+ const data = await resp.json();
729
+ diagnosticId = data.fixId;
730
+ serverIssues = data.knownIssues || [];
731
+ }
732
+ } catch {}
733
+ }
734
+ renderStatus(summary, issues, serverIssues);
735
+ rl.prompt();
736
+ return;
737
+ }
738
+
739
+ if (/^issues?$/i.test(input)) {
740
+ renderIssues(issues, serverIssues);
741
+ rl.prompt();
742
+ return;
743
+ }
744
+
745
+ if (/^status$/i.test(input)) {
746
+ renderStatus(summary, issues, serverIssues);
747
+ rl.prompt();
748
+ return;
749
+ }
750
+
751
+ // fix <id> — show details about a detected issue
752
+ const fixMatch = input.match(/^fix\s+(\d+)$/i);
753
+ if (fixMatch) {
754
+ const idx = parseInt(fixMatch[1]) - 1;
755
+ const allIssues = mergeIssues(issues, serverIssues);
756
+ if (idx < 0 || idx >= allIssues.length) {
757
+ console.log(c.red(` No issue #${fixMatch[1]}. Use ${c.cyan('issues')} to see the list.`));
758
+ } else {
759
+ const issue = allIssues[idx];
760
+ console.log('');
761
+ console.log(c.bold(` Issue #${idx + 1}: ${issue.title || issue.text}`));
762
+ console.log(` Severity: ${severityColor(issue.severity)}`);
763
+ if (issue.description) console.log(` ${issue.description}`);
764
+ if (issue.fix) {
765
+ console.log('');
766
+ console.log(c.dim(' Fix script:'));
767
+ console.log(c.dim(' ─────────────────────────────'));
768
+ for (const line of issue.fix.split('\n').slice(0, 15)) {
769
+ console.log(` ${c.dim(line)}`);
770
+ }
771
+ if (issue.fix.split('\n').length > 15) {
772
+ console.log(c.dim(` ... (${issue.fix.split('\n').length - 15} more lines)`));
773
+ }
774
+ console.log(c.dim(' ─────────────────────────────'));
775
+ console.log('');
776
+ console.log(` Run ${c.cyan(`apply ${idx + 1}`)} to apply this fix.`);
777
+ }
778
+ console.log('');
779
+ }
780
+ rl.prompt();
781
+ return;
782
+ }
783
+
784
+ // apply <id> — run fix with confirmation
785
+ const applyMatch = input.match(/^apply\s+(\d+)$/i);
786
+ if (applyMatch) {
787
+ const idx = parseInt(applyMatch[1]) - 1;
788
+ const allIssues = mergeIssues(issues, serverIssues);
789
+ if (idx < 0 || idx >= allIssues.length) {
790
+ console.log(c.red(` No issue #${applyMatch[1]}. Use ${c.cyan('issues')} to see the list.`));
791
+ rl.prompt();
792
+ return;
793
+ }
794
+
795
+ const issue = allIssues[idx];
796
+ if (!issue.fix) {
797
+ console.log(c.yellow(` No automatic fix available for this issue.`));
798
+ console.log(` Try asking about it: ${c.dim(`"how do I fix ${issue.title || issue.text}?"`)}`);
799
+ rl.prompt();
800
+ return;
801
+ }
802
+
803
+ console.log('');
804
+ console.log(c.bold(` Applying fix for: ${issue.title || issue.text}`));
805
+ console.log('');
806
+ for (const line of issue.fix.split('\n').slice(0, 10)) {
807
+ console.log(` ${c.dim(line)}`);
808
+ }
809
+ if (issue.fix.split('\n').length > 10) {
810
+ console.log(c.dim(` ... (${issue.fix.split('\n').length - 10} more lines)`));
811
+ }
812
+ console.log('');
813
+
814
+ const answer = await new Promise(resolve => {
815
+ rl.question(` ${c.yellow('Apply this fix?')} [y/N] `, resolve);
816
+ });
817
+
818
+ if (/^y(es)?$/i.test(answer.trim())) {
819
+ console.log('');
820
+ console.log(c.blue(' Running fix...'));
821
+ try {
822
+ const output = execSync(`bash -c ${JSON.stringify(issue.fix)}`, {
823
+ encoding: 'utf8',
824
+ timeout: 30000,
825
+ stdio: ['pipe', 'pipe', 'pipe'],
826
+ });
827
+ if (output.trim()) {
828
+ for (const line of output.trim().split('\n')) {
829
+ console.log(` ${line}`);
830
+ }
831
+ }
832
+ console.log(c.green(' ✅ Fix applied successfully.'));
833
+ console.log(c.dim(' Run "rescan" to verify.'));
834
+ } catch (err) {
835
+ console.log(c.red(` ❌ Fix failed: ${err.message}`));
836
+ }
837
+ } else {
838
+ console.log(c.dim(' Cancelled.'));
839
+ }
840
+ console.log('');
841
+ rl.prompt();
842
+ return;
843
+ }
844
+
845
+ // --- Natural language → send to /chat ---
846
+ console.log('');
847
+ await streamChat(input, diagnosticId, conversationId, rl);
848
+ console.log('');
849
+ rl.prompt();
850
+ });
851
+
852
+ rl.on('close', () => {
853
+ console.log('');
854
+ console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
855
+ console.log(c.cyan(' https://clawfix.dev'));
856
+ console.log('');
857
+ process.exit(0);
858
+ });
859
+ }
860
+
861
+ // ============================================================
862
+ // TUI Rendering helpers
863
+ // ============================================================
864
+
865
+ function renderStatus(summary, issues, serverIssues) {
866
+ process.stdout.write('\x1b[2J\x1b[H');
867
+ console.log('');
868
+ console.log(c.cyan(`🦞 ClawFix v${VERSION}`));
869
+ console.log(c.cyan('━'.repeat(48)));
503
870
  console.log('');
871
+ console.log(c.bold('System Status:'));
872
+ console.log(` ${summary.gateway.icon} Gateway: ${summary.gateway.label}`);
873
+ console.log(` ${summary.config.icon} Config: ${summary.config.label}`);
874
+ console.log(` ${summary.issues.icon} ${summary.issues.label}`);
875
+ console.log(` ${c.green('✓')} Node: ${summary.node} | OS: ${summary.os}`);
876
+ console.log('');
877
+
878
+ renderIssues(issues, serverIssues);
879
+
880
+ console.log(c.cyan('━'.repeat(48)));
881
+ console.log(c.dim(' Type naturally to chat, or: fix <#> | scan | apply <#> | help | exit'));
882
+ console.log('');
883
+ }
884
+
885
+ function renderIssues(issues, serverIssues) {
886
+ const all = mergeIssues(issues, serverIssues);
887
+
888
+ if (all.length === 0) {
889
+ console.log(c.green(' ✅ No issues detected — looking healthy!'));
890
+ console.log('');
891
+ return;
892
+ }
893
+
894
+ console.log(c.bold('Detected Issues:'));
895
+ for (let i = 0; i < all.length; i++) {
896
+ const issue = all[i];
897
+ const sev = issue.severity || 'medium';
898
+ const label = sev === 'critical' || sev === 'high'
899
+ ? c.red(`[${sev.toUpperCase()}]`)
900
+ : sev === 'medium'
901
+ ? c.yellow(`[${sev.toUpperCase()}]`)
902
+ : c.dim(`[${sev.toUpperCase()}]`);
903
+ console.log(` ${c.dim(`${i + 1}.`)} ${label} ${issue.title || issue.text}`);
904
+ }
905
+ console.log('');
906
+ }
907
+
908
+ function renderHelp() {
909
+ console.log('');
910
+ console.log(c.bold('Commands:'));
911
+ console.log(` ${c.cyan('fix <#>')} Show details + fix script for issue #`);
912
+ console.log(` ${c.cyan('apply <#>')} Apply the fix for issue # (with confirmation)`);
913
+ console.log(` ${c.cyan('scan')} Re-run diagnostics`);
914
+ console.log(` ${c.cyan('issues')} Show detected issues`);
915
+ console.log(` ${c.cyan('status')} Show system status`);
916
+ console.log(` ${c.cyan('help')} Show this help`);
917
+ console.log(` ${c.cyan('exit')} Quit ClawFix`);
918
+ console.log('');
919
+ console.log(c.bold('Chat:'));
920
+ console.log(` Just type naturally — e.g. ${c.dim('"my discord bot isn\'t responding"')}`);
921
+ console.log(` ClawFix AI will analyze using your diagnostic context.`);
922
+ console.log('');
923
+ }
924
+
925
+ /**
926
+ * Merge local CLI-detected issues with server-detected known issues.
927
+ * Server issues (from known-issues.js pattern matching) include fix scripts.
928
+ * Local issues are simpler {severity, text} objects.
929
+ * Deduplicate by rough text matching.
930
+ */
931
+ function mergeIssues(localIssues, serverIssues) {
932
+ const merged = [];
933
+ const seen = new Set();
934
+
935
+ // Server issues first (they have fix scripts)
936
+ if (serverIssues) {
937
+ for (const si of serverIssues) {
938
+ merged.push(si);
939
+ seen.add((si.title || '').toLowerCase());
940
+ }
941
+ }
942
+
943
+ // Then local issues that aren't duplicated
944
+ for (const li of localIssues) {
945
+ const key = (li.text || '').toLowerCase();
946
+ const isDup = [...seen].some(s =>
947
+ s.includes(key.slice(0, 20)) || key.includes(s.slice(0, 20))
948
+ );
949
+ if (!isDup) {
950
+ merged.push(li);
951
+ }
952
+ }
953
+
954
+ return merged;
955
+ }
956
+
957
+ function severityColor(sev) {
958
+ if (sev === 'critical') return c.red(c.bold('CRITICAL'));
959
+ if (sev === 'high') return c.red('HIGH');
960
+ if (sev === 'medium') return c.yellow('MEDIUM');
961
+ return c.dim('LOW');
962
+ }
963
+
964
+ // ============================================================
965
+ // Chat streaming — SSE from /api/chat
966
+ // ============================================================
967
+ async function streamChat(message, diagnosticId, conversationId, rl) {
968
+ // Pause readline so it doesn't interfere with output
969
+ rl.pause();
970
+
971
+ process.stdout.write(c.dim(' thinking...'));
972
+
973
+ try {
974
+ const resp = await fetch(`${API_URL}/api/chat`, {
975
+ method: 'POST',
976
+ headers: { 'Content-Type': 'application/json' },
977
+ body: JSON.stringify({ diagnosticId, message, conversationId }),
978
+ });
979
+
980
+ // Non-SSE fallback (e.g. AI not available)
981
+ const contentType = resp.headers.get('content-type') || '';
982
+ if (contentType.includes('application/json')) {
983
+ const data = await resp.json();
984
+ // Clear "thinking..."
985
+ process.stdout.write('\r\x1b[K');
986
+ if (data.error) {
987
+ console.log(c.red(` ${data.error}`));
988
+ } else {
989
+ wrapPrint(data.response || 'No response from AI.');
990
+ }
991
+ rl.resume();
992
+ return;
993
+ }
994
+
995
+ // SSE streaming
996
+ process.stdout.write('\r\x1b[K');
997
+ process.stdout.write(' ');
998
+
999
+ const reader = resp.body.getReader();
1000
+ const decoder = new TextDecoder();
1001
+ let buffer = '';
1002
+ let col = 2; // Current column (2 for indent)
1003
+
1004
+ while (true) {
1005
+ const { done, value } = await reader.read();
1006
+ if (done) break;
1007
+
1008
+ buffer += decoder.decode(value, { stream: true });
1009
+ const lines = buffer.split('\n');
1010
+ buffer = lines.pop() || '';
1011
+
1012
+ for (const line of lines) {
1013
+ const trimmed = line.trim();
1014
+ if (!trimmed || !trimmed.startsWith('data: ')) continue;
1015
+
1016
+ const data = trimmed.slice(6);
1017
+ if (data === '[DONE]') break;
1018
+
1019
+ try {
1020
+ const parsed = JSON.parse(data);
1021
+ if (parsed.error) {
1022
+ process.stdout.write(c.red(parsed.error));
1023
+ break;
1024
+ }
1025
+ if (parsed.content) {
1026
+ // Word-wrap at ~76 cols
1027
+ for (const ch of parsed.content) {
1028
+ if (ch === '\n') {
1029
+ process.stdout.write('\n ');
1030
+ col = 2;
1031
+ } else {
1032
+ process.stdout.write(ch);
1033
+ col++;
1034
+ if (col > 76 && ch === ' ') {
1035
+ process.stdout.write('\n ');
1036
+ col = 2;
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ } catch {}
1042
+ }
1043
+ }
1044
+
1045
+ process.stdout.write('\n');
1046
+ } catch (err) {
1047
+ process.stdout.write('\r\x1b[K');
1048
+ if (err.code === 'ECONNREFUSED' || err.cause?.code === 'ECONNREFUSED') {
1049
+ console.log(c.yellow(' ClawFix server is unreachable. Chat requires an internet connection.'));
1050
+ console.log(c.dim(' Local commands still work: fix <#>, apply <#>, scan, issues'));
1051
+ } else {
1052
+ console.log(c.red(` Connection error: ${err.message}`));
1053
+ }
1054
+ }
1055
+
1056
+ rl.resume();
1057
+ }
1058
+
1059
+ /**
1060
+ * Print text with 2-space indent and word wrapping.
1061
+ */
1062
+ function wrapPrint(text) {
1063
+ const width = 76;
1064
+ for (const paragraph of text.split('\n')) {
1065
+ if (!paragraph.trim()) {
1066
+ console.log('');
1067
+ continue;
1068
+ }
1069
+ const words = paragraph.split(' ');
1070
+ let line = ' ';
1071
+ for (const word of words) {
1072
+ if (line.length + word.length + 1 > width && line.trim()) {
1073
+ console.log(line);
1074
+ line = ' ';
1075
+ }
1076
+ line += (line.trim() ? ' ' : '') + word;
1077
+ }
1078
+ if (line.trim()) console.log(line);
1079
+ }
1080
+ }
1081
+
1082
+ // ============================================================
1083
+ // Main entry point
1084
+ // ============================================================
1085
+ async function main() {
1086
+ if (SHOW_HELP) {
1087
+ console.log(`
1088
+ 🦞 ClawFix v${VERSION} — AI-Powered OpenClaw Diagnostic
1089
+
1090
+ Usage: npx clawfix [options]
1091
+
1092
+ Modes:
1093
+ (default) Interactive TUI — scan + chat + fix
1094
+ --scan One-shot scan (legacy mode)
1095
+ --no-interactive Same as --scan
1096
+
1097
+ Options:
1098
+ --dry-run, -n Scan locally only — shows what would be collected, sends nothing
1099
+ --show-data, -d Display the full diagnostic payload before asking to send
1100
+ --yes, -y Skip confirmation prompt and send automatically
1101
+ --help, -h Show this help message
1102
+
1103
+ Environment:
1104
+ CLAWFIX_API Override API URL (default: https://clawfix.dev)
1105
+ CLAWFIX_AUTO=1 Same as --yes
1106
+
1107
+ Interactive Commands:
1108
+ fix <#> Show details + fix script for a detected issue
1109
+ apply <#> Apply the fix (with confirmation)
1110
+ scan Re-run diagnostics
1111
+ issues Show detected issues
1112
+ help Show help
1113
+ exit Quit
1114
+
1115
+ Or just type naturally to chat with ClawFix AI.
1116
+
1117
+ Security:
1118
+ • All API keys, tokens, and passwords are automatically redacted
1119
+ • Your hostname is SHA-256 hashed (only first 8 chars sent)
1120
+ • No file contents are read (only existence checks)
1121
+ • Nothing is sent without your explicit approval (unless --yes)
1122
+ • Source code: https://github.com/arcabotai/clawfix
1123
+
1124
+ Examples:
1125
+ npx clawfix # Interactive TUI (default)
1126
+ npx clawfix --scan # One-shot scan + AI analysis
1127
+ npx clawfix --dry-run # See what data would be collected
1128
+ npx clawfix --yes --scan # Auto-send for CI/scripting
1129
+ `);
1130
+ return;
1131
+ }
1132
+
1133
+ if (ONE_SHOT) {
1134
+ await runOneShotMode();
1135
+ } else {
1136
+ await runInteractiveMode();
1137
+ }
504
1138
  }
505
1139
 
506
1140
  main().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawfix",
3
- "version": "0.2.1",
3
+ "version": "0.6.0",
4
4
  "description": "AI-powered diagnostic and repair for OpenClaw installations",
5
5
  "bin": {
6
6
  "clawfix": "./bin/clawfix.js"
@@ -18,10 +18,10 @@
18
18
  "homepage": "https://clawfix.dev",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "https://github.com/arcaboteth/clawfix"
21
+ "url": "https://github.com/arcabotai/clawfix"
22
22
  },
23
23
  "bugs": {
24
- "url": "https://github.com/arcaboteth/clawfix/issues"
24
+ "url": "https://github.com/arcabotai/clawfix/issues"
25
25
  },
26
26
  "author": "Arca <arca@arcabot.ai> (https://arcabot.ai)",
27
27
  "license": "MIT",