clawfix 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arcabot AI (arcabot.ai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # 🦞 ClawFix
2
+
3
+ AI-powered diagnostic and repair for [OpenClaw](https://openclaw.ai) installations.
4
+
5
+ One command. No signup. No account.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx clawfix
11
+ ```
12
+
13
+ That's it. ClawFix scans your OpenClaw setup, finds issues, and generates fix scripts.
14
+
15
+ ## What it does
16
+
17
+ 1. **Scans** your OpenClaw installation (config, gateway, plugins, workspace, logs)
18
+ 2. **Detects** issues using pattern matching (12+ known issue detectors)
19
+ 3. **Analyzes** novel problems with AI (optional, with your consent)
20
+ 4. **Generates** a fix script you can review and run
21
+
22
+ ## Privacy
23
+
24
+ - All secrets, tokens, and API keys are **automatically redacted** before leaving your machine
25
+ - Diagnostic data is only sent with your **explicit consent**
26
+ - No telemetry, no tracking, no account required
27
+ - [Source code is open](https://github.com/arcaboteth/clawfix) — verify it yourself
28
+
29
+ ## Options
30
+
31
+ ```bash
32
+ npx clawfix --yes # Skip confirmation, auto-send diagnostic
33
+ npx clawfix -y # Same as above
34
+ ```
35
+
36
+ ## Environment
37
+
38
+ | Variable | Description |
39
+ |----------|-------------|
40
+ | `CLAWFIX_API` | API endpoint (default: `https://clawfix.dev`) |
41
+ | `CLAWFIX_AUTO` | Set to `1` to auto-send without prompt |
42
+
43
+ ## Alternative
44
+
45
+ Don't want Node.js? Use the bash script directly:
46
+
47
+ ```bash
48
+ curl -sSL clawfix.dev/fix | bash
49
+ ```
50
+
51
+ ## Links
52
+
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)
56
+ - **Made by:** [Arca](https://arcabot.ai) (arcabot.eth)
57
+
58
+ ## License
59
+
60
+ MIT
package/bin/clawfix.js ADDED
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ClawFix CLI — AI-powered OpenClaw diagnostic & repair
5
+ * https://clawfix.dev
6
+ *
7
+ * Usage: npx clawfix
8
+ */
9
+
10
+ import { readFile, access, readdir, stat } from 'node:fs/promises';
11
+ import { execSync } from 'node:child_process';
12
+ import { homedir, platform, arch, release, hostname } from 'node:os';
13
+ import { join } from 'node:path';
14
+ import { createHash } from 'node:crypto';
15
+
16
+ // --- Config ---
17
+ const API_URL = process.env.CLAWFIX_API || 'https://clawfix.dev';
18
+ const VERSION = '0.1.0';
19
+
20
+ // --- Colors ---
21
+ const c = {
22
+ red: s => `\x1b[31m${s}\x1b[0m`,
23
+ green: s => `\x1b[32m${s}\x1b[0m`,
24
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
25
+ blue: s => `\x1b[34m${s}\x1b[0m`,
26
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
27
+ bold: s => `\x1b[1m${s}\x1b[0m`,
28
+ dim: s => `\x1b[2m${s}\x1b[0m`,
29
+ };
30
+
31
+ // --- Helpers ---
32
+ async function exists(p) {
33
+ try { await access(p); return true; } catch { return false; }
34
+ }
35
+
36
+ async function readJson(p) {
37
+ try { return JSON.parse(await readFile(p, 'utf8')); } catch { return null; }
38
+ }
39
+
40
+ function run(cmd) {
41
+ try { return execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim(); } catch { return ''; }
42
+ }
43
+
44
+ function hashStr(s) {
45
+ return createHash('sha256').update(s).digest('hex').slice(0, 8);
46
+ }
47
+
48
+ // Redact secrets from config
49
+ function sanitizeConfig(config) {
50
+ if (!config || typeof config !== 'object') return config;
51
+
52
+ const redact = (obj) => {
53
+ if (typeof obj === 'string') {
54
+ if (obj.length > 20 && /^(sk-|xai-|eyJ|ghp_|gho_|npm_|m0-|AIza|ntn_)/.test(obj)) return '***REDACTED***';
55
+ if (obj.length > 40 && /^[A-Za-z0-9+/=]+$/.test(obj)) return '***REDACTED***';
56
+ return obj;
57
+ }
58
+ if (Array.isArray(obj)) return obj.map(redact);
59
+ if (obj && typeof obj === 'object') {
60
+ const result = {};
61
+ for (const [k, v] of Object.entries(obj)) {
62
+ if (/key|token|secret|password|jwt|apikey|accesstoken/i.test(k)) {
63
+ result[k] = '***REDACTED***';
64
+ } else if (k === 'env') {
65
+ continue; // Skip env block entirely
66
+ } else {
67
+ result[k] = redact(v);
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+ return obj;
73
+ };
74
+
75
+ return redact(config);
76
+ }
77
+
78
+ // --- Main ---
79
+ async function main() {
80
+ console.log('');
81
+ console.log(c.cyan(`🦞 ClawFix v${VERSION} — AI-Powered OpenClaw Diagnostic`));
82
+ console.log(c.cyan('━'.repeat(50)));
83
+ console.log('');
84
+
85
+ // --- Detect OpenClaw ---
86
+ const home = homedir();
87
+ const openclawDir = await exists(join(home, '.openclaw')) ? join(home, '.openclaw') :
88
+ await exists(join(home, '.config', 'openclaw')) ? join(home, '.config', 'openclaw') : null;
89
+
90
+ const openclawBin = run('which openclaw') ||
91
+ (await exists('/opt/homebrew/bin/openclaw') ? '/opt/homebrew/bin/openclaw' : '') ||
92
+ (await exists('/usr/local/bin/openclaw') ? '/usr/local/bin/openclaw' : '');
93
+
94
+ const configPath = openclawDir ? join(openclawDir, 'openclaw.json') : null;
95
+
96
+ if (!openclawBin && !openclawDir) {
97
+ console.log(c.red('❌ OpenClaw not found on this system.'));
98
+ console.log('Make sure OpenClaw is installed: https://openclaw.ai');
99
+ process.exit(1);
100
+ }
101
+
102
+ console.log(c.green('✅ OpenClaw found'));
103
+ if (openclawBin) console.log(` Binary: ${openclawBin}`);
104
+ if (openclawDir) console.log(` Config: ${openclawDir}`);
105
+
106
+ // --- System Info ---
107
+ console.log('');
108
+ console.log(c.blue('📋 Collecting system information...'));
109
+
110
+ const osName = platform();
111
+ const osVersion = release();
112
+ const osArch = arch();
113
+ const nodeVersion = process.version;
114
+ const npmVersion = run('npm --version');
115
+ const hostHash = hashStr(hostname());
116
+
117
+ let ocVersion = '';
118
+ if (openclawBin) {
119
+ ocVersion = run(`"${openclawBin}" --version`);
120
+ }
121
+
122
+ console.log(` OS: ${osName} ${osVersion} (${osArch})`);
123
+ console.log(` Node: ${nodeVersion}`);
124
+ console.log(` OpenClaw: ${ocVersion || 'not found'}`);
125
+
126
+ // --- Read Config ---
127
+ console.log('');
128
+ console.log(c.blue('🔒 Reading config (secrets will be redacted)...'));
129
+
130
+ let config = null;
131
+ let sanitizedConfig = {};
132
+
133
+ if (configPath && await exists(configPath)) {
134
+ config = await readJson(configPath);
135
+ sanitizedConfig = sanitizeConfig(config) || {};
136
+ console.log(c.green(' ✅ Config read and sanitized'));
137
+ } else {
138
+ console.log(c.yellow(' ⚠️ No config file found'));
139
+ }
140
+
141
+ // --- Gateway Status ---
142
+ console.log('');
143
+ console.log(c.blue('🔌 Checking gateway status...'));
144
+
145
+ let gatewayStatus = 'unknown';
146
+ if (openclawBin) {
147
+ gatewayStatus = run(`"${openclawBin}" gateway status 2>&1`) || 'could not check';
148
+ }
149
+
150
+ const gatewayPort = config?.gateway?.port || 18789;
151
+ const gatewayPid = run('pgrep -f "openclaw.*gateway"') || '';
152
+
153
+ console.log(` Status: ${gatewayStatus.split('\n')[0]}`);
154
+ if (gatewayPid) console.log(` PID: ${gatewayPid}`);
155
+ console.log(` Port: ${gatewayPort}`);
156
+
157
+ // --- Logs ---
158
+ console.log('');
159
+ console.log(c.blue('📜 Reading recent logs...'));
160
+
161
+ let errorLogs = '';
162
+ let stderrLogs = '';
163
+
164
+ const logPath = openclawDir ? join(openclawDir, 'logs', 'gateway.log') : null;
165
+ const errLogPath = openclawDir ? join(openclawDir, 'logs', 'gateway.err.log') : null;
166
+
167
+ if (logPath && await exists(logPath)) {
168
+ try {
169
+ const logContent = await readFile(logPath, 'utf8');
170
+ const lines = logContent.split('\n');
171
+ errorLogs = lines
172
+ .filter(l => /error|warn|fail|crash|EADDRINUSE|EACCES/i.test(l))
173
+ .slice(-30)
174
+ .join('\n');
175
+ console.log(c.green(` ✅ Gateway log found (${lines.length} lines)`));
176
+ } catch {}
177
+ }
178
+
179
+ if (errLogPath && await exists(errLogPath)) {
180
+ try {
181
+ stderrLogs = (await readFile(errLogPath, 'utf8')).split('\n').slice(-50).join('\n');
182
+ console.log(c.green(' ✅ Error log found'));
183
+ } catch {}
184
+ }
185
+
186
+ // --- Plugins ---
187
+ console.log('');
188
+ console.log(c.blue('🔌 Checking plugins...'));
189
+
190
+ const plugins = config?.plugins?.entries || {};
191
+ for (const [name, cfg] of Object.entries(plugins)) {
192
+ const icon = cfg.enabled === false ? '❌' : '✅';
193
+ console.log(` ${icon} ${name}`);
194
+ }
195
+
196
+ // --- Workspace ---
197
+ console.log('');
198
+ console.log(c.blue('📁 Checking workspace...'));
199
+
200
+ const workspaceDir = config?.agents?.defaults?.workspace || '';
201
+ let mdFiles = 0;
202
+ let memoryFiles = 0;
203
+ let hasSoul = false;
204
+ let hasAgents = false;
205
+
206
+ if (workspaceDir && await exists(workspaceDir)) {
207
+ hasSoul = await exists(join(workspaceDir, 'SOUL.md'));
208
+ hasAgents = await exists(join(workspaceDir, 'AGENTS.md'));
209
+
210
+ try {
211
+ const files = run(`find "${workspaceDir}" -name "*.md" 2>/dev/null | wc -l`);
212
+ mdFiles = parseInt(files) || 0;
213
+ } catch {}
214
+
215
+ const memDir = join(workspaceDir, 'memory');
216
+ if (await exists(memDir)) {
217
+ try {
218
+ const mFiles = await readdir(memDir);
219
+ memoryFiles = mFiles.filter(f => f.endsWith('.md')).length;
220
+ } catch {}
221
+ }
222
+
223
+ console.log(` Path: ${workspaceDir}`);
224
+ console.log(` Files: ${mdFiles} .md files`);
225
+ console.log(` Memory: ${memoryFiles} daily notes`);
226
+ console.log(` SOUL.md: ${hasSoul}`);
227
+ console.log(` AGENTS.md: ${hasAgents}`);
228
+ }
229
+
230
+ // --- Check Ports ---
231
+ console.log('');
232
+ console.log(c.blue('🔗 Checking port availability...'));
233
+
234
+ const checkPort = (port, name) => {
235
+ const inUse = run(`lsof -i :${port} 2>/dev/null | grep LISTEN`) ||
236
+ run(`ss -tlnp 2>/dev/null | grep :${port}`);
237
+ if (inUse) {
238
+ console.log(c.yellow(` ⚠️ Port ${port} (${name}) — IN USE`));
239
+ return true;
240
+ } else {
241
+ console.log(c.green(` ✅ Port ${port} (${name}) — available`));
242
+ return false;
243
+ }
244
+ };
245
+
246
+ checkPort(gatewayPort, 'gateway');
247
+ checkPort(18800, 'browser CDP');
248
+ checkPort(18791, 'browser control');
249
+
250
+ // --- Local Issue Detection ---
251
+ console.log('');
252
+ console.log(c.cyan('━'.repeat(50)));
253
+ console.log(c.bold('📊 Diagnostic Summary'));
254
+ console.log(c.cyan('━'.repeat(50)));
255
+ console.log('');
256
+
257
+ const issues = [];
258
+
259
+ if (/error|not running|failed/i.test(gatewayStatus)) {
260
+ issues.push({ severity: 'critical', text: 'Gateway is not running' });
261
+ }
262
+ if (/EADDRINUSE/i.test(errorLogs)) {
263
+ issues.push({ severity: 'critical', text: 'Port conflict detected' });
264
+ }
265
+ if (config?.plugins?.entries?.['openclaw-mem0']?.config?.enableGraph === true) {
266
+ issues.push({ severity: 'high', text: 'Mem0 enableGraph requires Pro plan (will silently fail)' });
267
+ }
268
+ if (!config?.agents?.defaults?.memorySearch?.query?.hybrid?.enabled) {
269
+ issues.push({ severity: 'medium', text: 'Hybrid search not enabled (recommended)' });
270
+ }
271
+ if (!config?.agents?.defaults?.contextPruning) {
272
+ issues.push({ severity: 'medium', text: 'No context pruning configured' });
273
+ }
274
+ if (!config?.agents?.defaults?.compaction?.memoryFlush?.enabled) {
275
+ issues.push({ severity: 'medium', text: 'Memory flush not enabled (data loss on compaction)' });
276
+ }
277
+ if (!hasSoul && workspaceDir) {
278
+ issues.push({ severity: 'low', text: 'No SOUL.md found (agent has no personality)' });
279
+ }
280
+ if (memoryFiles === 0 && workspaceDir) {
281
+ issues.push({ severity: 'low', text: 'No memory files found' });
282
+ }
283
+
284
+ if (issues.length === 0) {
285
+ console.log(c.green('✅ No issues detected! Your OpenClaw looks healthy.'));
286
+ } else {
287
+ console.log(c.red(`Found ${issues.length} issue(s):`));
288
+ console.log('');
289
+ for (const issue of issues) {
290
+ const icon = issue.severity === 'critical' ? c.red('❌') :
291
+ issue.severity === 'high' ? c.red('❌') :
292
+ c.yellow('⚠️');
293
+ console.log(` ${icon} [${issue.severity.toUpperCase()}] ${issue.text}`);
294
+ }
295
+ }
296
+
297
+ console.log('');
298
+ console.log(c.cyan('━'.repeat(50)));
299
+ console.log('');
300
+
301
+ // --- Build Payload ---
302
+ const diagnostic = {
303
+ version: VERSION,
304
+ timestamp: new Date().toISOString(),
305
+ hostHash,
306
+ system: {
307
+ os: osName,
308
+ osVersion,
309
+ arch: osArch,
310
+ nodeVersion,
311
+ npmVersion,
312
+ },
313
+ openclaw: {
314
+ version: ocVersion || 'unknown',
315
+ binary: openclawBin || 'not found',
316
+ configDir: openclawDir || 'not found',
317
+ gatewayStatus,
318
+ gatewayPid: gatewayPid || 'none',
319
+ gatewayPort,
320
+ },
321
+ config: sanitizedConfig,
322
+ logs: {
323
+ errors: errorLogs,
324
+ stderr: stderrLogs,
325
+ },
326
+ workspace: {
327
+ path: workspaceDir || 'unknown',
328
+ mdFiles,
329
+ memoryFiles,
330
+ hasSoul,
331
+ hasAgents,
332
+ },
333
+ browser: {
334
+ status: openclawDir && await exists(join(openclawDir, 'browser')) ? 'configured' : 'not configured',
335
+ },
336
+ };
337
+
338
+ // --- Send for AI analysis ---
339
+ if (issues.length === 0) {
340
+ console.log(c.green('Your OpenClaw is looking good! No fixes needed.'));
341
+ console.log(`If you're still having issues, set CLAWFIX_VERBOSE=1 and run again.`);
342
+ console.log('');
343
+ console.log(c.cyan(`🦞 ClawFix — made by Arca (arcabot.eth)`));
344
+ console.log(c.cyan(` https://clawfix.dev | https://x.com/arcaboteth`));
345
+ console.log('');
346
+ return;
347
+ }
348
+
349
+ console.log(c.bold('Want AI-powered fixes? Send this diagnostic for analysis.'));
350
+ console.log('All secrets are redacted. No private data is sent.');
351
+ console.log('');
352
+
353
+ // Check if running in non-interactive mode
354
+ const autoSend = process.env.CLAWFIX_AUTO === '1' || process.argv.includes('--yes') || process.argv.includes('-y');
355
+
356
+ let shouldSend = autoSend;
357
+ if (!shouldSend) {
358
+ const readline = await import('node:readline');
359
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
360
+ const answer = await new Promise(resolve => {
361
+ rl.question('Send diagnostic for AI analysis? [y/N] ', resolve);
362
+ });
363
+ rl.close();
364
+ shouldSend = /^y(es)?$/i.test(answer.trim());
365
+ }
366
+
367
+ if (!shouldSend) {
368
+ console.log('');
369
+ console.log('No problem! You can send it manually:');
370
+ console.log(c.cyan(` npx clawfix --yes`));
371
+ console.log('');
372
+ return;
373
+ }
374
+
375
+ console.log('');
376
+ console.log(c.blue('📡 Sending diagnostic to ClawFix...'));
377
+
378
+ try {
379
+ const response = await fetch(`${API_URL}/api/diagnose`, {
380
+ method: 'POST',
381
+ headers: { 'Content-Type': 'application/json' },
382
+ body: JSON.stringify(diagnostic),
383
+ });
384
+
385
+ if (!response.ok) {
386
+ throw new Error(`API returned ${response.status}: ${await response.text()}`);
387
+ }
388
+
389
+ const result = await response.json();
390
+ const fixId = result.fixId;
391
+
392
+ console.log('');
393
+ console.log(c.green(`✅ Diagnosis complete! Found ${result.issuesFound} issue(s).`));
394
+ console.log('');
395
+
396
+ // Show known issues
397
+ if (result.knownIssues) {
398
+ for (const issue of result.knownIssues) {
399
+ console.log(` ${issue.severity.toUpperCase()} — ${issue.title}: ${issue.description}`);
400
+ }
401
+ }
402
+
403
+ console.log('');
404
+ console.log(c.bold('AI Analysis:'));
405
+ console.log(result.analysis || 'Pattern matching only (no AI configured)');
406
+ console.log('');
407
+
408
+ // Save fix script
409
+ if (result.fixScript) {
410
+ const { writeFile } = await import('node:fs/promises');
411
+ const fixPath = `/tmp/clawfix-${fixId}.sh`;
412
+ await writeFile(fixPath, result.fixScript);
413
+
414
+ console.log(c.cyan('━'.repeat(50)));
415
+ console.log('');
416
+ console.log(c.bold(`📋 Fix script saved to: ${fixPath}`));
417
+ console.log(` Review it: ${c.cyan(`cat ${fixPath}`)}`);
418
+ console.log(` Apply it: ${c.cyan(`bash ${fixPath}`)}`);
419
+ console.log('');
420
+ console.log(c.bold('🌐 View results in browser:'));
421
+ console.log(` ${c.cyan(`${API_URL}/results/${fixId}`)}`);
422
+ console.log('');
423
+ console.log(`${c.bold('Fix ID:')} ${fixId}`);
424
+ }
425
+ } catch (err) {
426
+ console.log(c.red(`❌ Error: ${err.message}`));
427
+ console.log('');
428
+ console.log('Try the web version instead:');
429
+ console.log(c.cyan(' curl -sSL clawfix.dev/fix | bash'));
430
+ }
431
+
432
+ console.log('');
433
+ console.log(c.cyan('🦞 ClawFix — made by Arca (arcabot.eth)'));
434
+ console.log(c.cyan(' https://clawfix.dev | https://x.com/arcaboteth'));
435
+ console.log('');
436
+ }
437
+
438
+ main().catch(err => {
439
+ console.error(c.red(`Fatal error: ${err.message}`));
440
+ process.exit(1);
441
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "clawfix",
3
+ "version": "0.1.0",
4
+ "description": "AI-powered diagnostic and repair for OpenClaw installations",
5
+ "bin": {
6
+ "clawfix": "./bin/clawfix.js"
7
+ },
8
+ "type": "module",
9
+ "keywords": [
10
+ "openclaw",
11
+ "ai",
12
+ "diagnostic",
13
+ "repair",
14
+ "agent",
15
+ "fix",
16
+ "devtools"
17
+ ],
18
+ "homepage": "https://clawfix.dev",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/arcaboteth/clawfix"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/arcaboteth/clawfix/issues"
25
+ },
26
+ "author": "Arca <arca@arcabot.ai> (https://arcabot.ai)",
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "files": [
32
+ "bin/",
33
+ "README.md",
34
+ "LICENSE"
35
+ ]
36
+ }