clawfix 0.2.0 → 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 +762 -122
  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,52 +157,125 @@ Examples:
190
157
  const gatewayPort = config?.gateway?.port || 18789;
191
158
  const gatewayPid = run('pgrep -f "openclaw.*gateway"') || '';
192
159
 
193
- console.log(` Status: ${gatewayStatus.split('\n')[0]}`);
194
- if (gatewayPid) console.log(` PID: ${gatewayPid}`);
195
- console.log(` Port: ${gatewayPort}`);
160
+ const statusLine = gatewayStatus.split('\n').find(l => /runtime:|listening|running|stopped|not running/i.test(l))
161
+ || gatewayStatus.split('\n')[0];
162
+ log(` Status: ${statusLine.trim()}`);
163
+ if (gatewayPid) log(` PID: ${gatewayPid}`);
164
+ log(` Port: ${gatewayPort}`);
196
165
 
197
166
  // --- Logs ---
198
- console.log('');
199
- console.log(c.blue('📜 Reading recent logs...'));
167
+ log('');
168
+ log(c.blue('📜 Reading recent logs...'));
200
169
 
201
170
  let errorLogs = '';
202
171
  let stderrLogs = '';
172
+ let gatewayLogTail = '';
173
+ let errLogSizeMB = 0;
174
+ let logSizeMB = 0;
203
175
 
204
176
  const logPath = openclawDir ? join(openclawDir, 'logs', 'gateway.log') : null;
205
177
  const errLogPath = openclawDir ? join(openclawDir, 'logs', 'gateway.err.log') : null;
206
178
 
207
179
  if (logPath && await exists(logPath)) {
208
180
  try {
209
- const logContent = await readFile(logPath, 'utf8');
210
- 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');
211
185
  errorLogs = lines
212
186
  .filter(l => /error|warn|fail|crash|EADDRINUSE|EACCES/i.test(l))
213
187
  .slice(-30)
214
188
  .join('\n');
215
- 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)`));
216
194
  } catch {}
217
195
  }
218
196
 
219
197
  if (errLogPath && await exists(errLogPath)) {
220
198
  try {
221
- stderrLogs = (await readFile(errLogPath, 'utf8')).split('\n').slice(-50).join('\n');
222
- 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!' : ''})`);
223
204
  } catch {}
224
205
  }
225
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
+
226
266
  // --- Plugins ---
227
- console.log('');
228
- console.log(c.blue('🔌 Checking plugins...'));
267
+ log('');
268
+ log(c.blue('🔌 Checking plugins...'));
229
269
 
230
270
  const plugins = config?.plugins?.entries || {};
231
271
  for (const [name, cfg] of Object.entries(plugins)) {
232
272
  const icon = cfg.enabled === false ? '❌' : '✅';
233
- console.log(` ${icon} ${name}`);
273
+ log(` ${icon} ${name}`);
234
274
  }
235
275
 
236
276
  // --- Workspace ---
237
- console.log('');
238
- console.log(c.blue('📁 Checking workspace...'));
277
+ log('');
278
+ log(c.blue('📁 Checking workspace...'));
239
279
 
240
280
  const workspaceDir = config?.agents?.defaults?.workspace || '';
241
281
  let mdFiles = 0;
@@ -260,25 +300,28 @@ Examples:
260
300
  } catch {}
261
301
  }
262
302
 
263
- console.log(` Path: ${workspaceDir}`);
264
- console.log(` Files: ${mdFiles} .md files`);
265
- console.log(` Memory: ${memoryFiles} daily notes`);
266
- console.log(` SOUL.md: ${hasSoul}`);
267
- 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}`);
268
308
  }
269
309
 
270
310
  // --- Check Ports ---
271
- console.log('');
272
- console.log(c.blue('🔗 Checking port availability...'));
311
+ log('');
312
+ log(c.blue('🔗 Checking port availability...'));
273
313
 
314
+ const portResults = {};
274
315
  const checkPort = (port, name) => {
275
316
  const inUse = run(`lsof -i :${port} 2>/dev/null | grep LISTEN`) ||
276
317
  run(`ss -tlnp 2>/dev/null | grep :${port}`);
277
318
  if (inUse) {
278
- console.log(c.yellow(` ⚠️ Port ${port} (${name}) — IN USE`));
319
+ log(c.yellow(` ⚠️ Port ${port} (${name}) — IN USE`));
320
+ portResults[port] = true;
279
321
  return true;
280
322
  } else {
281
- console.log(c.green(` ✅ Port ${port} (${name}) — available`));
323
+ log(c.green(` ✅ Port ${port} (${name}) — available`));
324
+ portResults[port] = false;
282
325
  return false;
283
326
  }
284
327
  };
@@ -288,20 +331,50 @@ Examples:
288
331
  checkPort(18791, 'browser control');
289
332
 
290
333
  // --- Local Issue Detection ---
291
- console.log('');
292
- console.log(c.cyan('━'.repeat(50)));
293
- console.log(c.bold('📊 Diagnostic Summary'));
294
- console.log(c.cyan('━'.repeat(50)));
295
- console.log('');
296
-
297
334
  const issues = [];
298
335
 
299
- if (/error|not running|failed/i.test(gatewayStatus)) {
336
+ const gatewayRunning = /running.*pid|state active|listening/i.test(gatewayStatus);
337
+ const gatewayFailed = /not running|failed to start|stopped|inactive/i.test(gatewayStatus);
338
+ if (gatewayFailed || (!gatewayRunning && !/warning/i.test(gatewayStatus))) {
300
339
  issues.push({ severity: 'critical', text: 'Gateway is not running' });
301
340
  }
302
341
  if (/EADDRINUSE/i.test(errorLogs)) {
303
342
  issues.push({ severity: 'critical', text: 'Port conflict detected' });
304
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
+
305
378
  if (config?.plugins?.entries?.['openclaw-mem0']?.config?.enableGraph === true) {
306
379
  issues.push({ severity: 'high', text: 'Mem0 enableGraph requires Pro plan (will silently fail)' });
307
380
  }
@@ -321,23 +394,6 @@ Examples:
321
394
  issues.push({ severity: 'low', text: 'No memory files found' });
322
395
  }
323
396
 
324
- if (issues.length === 0) {
325
- console.log(c.green('✅ No issues detected! Your OpenClaw looks healthy.'));
326
- } else {
327
- console.log(c.red(`Found ${issues.length} issue(s):`));
328
- console.log('');
329
- for (const issue of issues) {
330
- const icon = issue.severity === 'critical' ? c.red('❌') :
331
- issue.severity === 'high' ? c.red('❌') :
332
- c.yellow('⚠️');
333
- console.log(` ${icon} [${issue.severity.toUpperCase()}] ${issue.text}`);
334
- }
335
- }
336
-
337
- console.log('');
338
- console.log(c.cyan('━'.repeat(50)));
339
- console.log('');
340
-
341
397
  // --- Build Payload ---
342
398
  const diagnostic = {
343
399
  version: VERSION,
@@ -362,7 +418,11 @@ Examples:
362
418
  logs: {
363
419
  errors: errorLogs,
364
420
  stderr: stderrLogs,
421
+ gatewayLog: gatewayLogTail,
422
+ errLogSizeMB,
423
+ logSizeMB,
365
424
  },
425
+ service: serviceHealth,
366
426
  workspace: {
367
427
  path: workspaceDir || 'unknown',
368
428
  mdFiles,
@@ -375,6 +435,72 @@ Examples:
375
435
  },
376
436
  };
377
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
+
378
504
  // --- Show collected data ---
379
505
  if (DRY_RUN || SHOW_DATA) {
380
506
  console.log('');
@@ -392,18 +518,17 @@ Examples:
392
518
  console.log(c.cyan(' npx clawfix'));
393
519
  console.log('');
394
520
  console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
395
- console.log(c.cyan(' https://clawfix.dev | https://x.com/arcaboteth'));
521
+ console.log(c.cyan(' https://clawfix.dev | https://x.com/arcabotai'));
396
522
  console.log('');
397
523
  return;
398
524
  }
399
525
 
400
- // --- Send for AI analysis ---
401
526
  if (issues.length === 0) {
402
527
  console.log(c.green('Your OpenClaw is looking good! No fixes needed.'));
403
528
  console.log(`If you're still having issues, run with --show-data to see what would be collected.`);
404
529
  console.log('');
405
530
  console.log(c.cyan(`🦞 ClawFix — made by Arca (arcabot.eth)`));
406
- console.log(c.cyan(` https://clawfix.dev | https://x.com/arcaboteth`));
531
+ console.log(c.cyan(` https://clawfix.dev | https://x.com/arcabotai`));
407
532
  console.log('');
408
533
  return;
409
534
  }
@@ -417,8 +542,7 @@ Examples:
417
542
 
418
543
  let shouldSend = AUTO_SEND;
419
544
  if (!shouldSend) {
420
- const readline = await import('node:readline');
421
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
545
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
422
546
  const answer = await new Promise(resolve => {
423
547
  rl.question('Send diagnostic for AI analysis? [y/N] ', resolve);
424
548
  });
@@ -455,7 +579,6 @@ Examples:
455
579
  console.log(c.green(`✅ Diagnosis complete! Found ${result.issuesFound} issue(s).`));
456
580
  console.log('');
457
581
 
458
- // Show known issues
459
582
  if (result.knownIssues) {
460
583
  for (const issue of result.knownIssues) {
461
584
  console.log(` ${issue.severity.toUpperCase()} — ${issue.title}: ${issue.description}`);
@@ -467,7 +590,6 @@ Examples:
467
590
  console.log(result.analysis || 'Pattern matching only (no AI configured)');
468
591
  console.log('');
469
592
 
470
- // Save fix script
471
593
  if (result.fixScript) {
472
594
  const { writeFile } = await import('node:fs/promises');
473
595
  const fixPath = `/tmp/clawfix-${fixId}.sh`;
@@ -493,8 +615,526 @@ Examples:
493
615
 
494
616
  console.log('');
495
617
  console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
496
- 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)));
497
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
+ }
498
1138
  }
499
1139
 
500
1140
  main().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawfix",
3
- "version": "0.2.0",
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",