clawkit-doctor 1.0.0 ā 2.0.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/bin/index.js +343 -70
- package/package.json +4 -4
package/bin/index.js
CHANGED
|
@@ -6,113 +6,386 @@ const os = require('os');
|
|
|
6
6
|
const chalk = require('chalk');
|
|
7
7
|
const ora = require('ora');
|
|
8
8
|
const http = require('http');
|
|
9
|
+
const net = require('net');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
// --- Configuration ---
|
|
13
|
+
const SITE_URL = 'https://getclawkit.com';
|
|
14
|
+
const CONFIG_DIR = path.join(os.homedir(), '.openclaw');
|
|
15
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'clawhub.json');
|
|
16
|
+
const SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');
|
|
17
|
+
const FIX_MODE = process.argv.includes('--fix');
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
]
|
|
19
|
+
// --- Report collector ---
|
|
20
|
+
const report = {
|
|
21
|
+
ts: new Date().toISOString(),
|
|
22
|
+
os: `${os.platform()} ${os.arch()} ${os.release()}`,
|
|
23
|
+
node: process.version,
|
|
24
|
+
fix: FIX_MODE,
|
|
25
|
+
checks: [],
|
|
26
|
+
};
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
function record(id, status, message, helpUrl) {
|
|
29
|
+
report.checks.push({ id, status, message });
|
|
30
|
+
return { id, status, message, helpUrl };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --- Helpers ---
|
|
34
|
+
function commandExists(cmd) {
|
|
35
|
+
try {
|
|
36
|
+
const where = os.platform() === 'win32' ? 'where' : 'which';
|
|
37
|
+
execSync(`${where} ${cmd}`, { stdio: 'ignore' });
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
24
41
|
}
|
|
25
|
-
console.log(chalk.cyan.bold('\nā
Diagnosis Complete. Screenshot this if you need help!\n'));
|
|
26
|
-
console.log(chalk.gray('For more help, visit: https://getclawkit.com/docs\n'));
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
44
|
+
function getCommandVersion(cmd) {
|
|
45
|
+
try {
|
|
46
|
+
return execSync(`${cmd} --version`, { encoding: 'utf8', timeout: 5000 }).trim().split('\n')[0];
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkPort(port) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const server = net.createServer();
|
|
55
|
+
server.listen(port, '127.0.0.1', () => {
|
|
56
|
+
server.close(() => resolve(false)); // port is free
|
|
57
|
+
});
|
|
58
|
+
server.on('error', () => resolve(true)); // port is in use
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readConfig() {
|
|
63
|
+
try {
|
|
64
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
65
|
+
return JSON.parse(raw);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getNestedValue(obj, keyPath) {
|
|
72
|
+
return keyPath.split('.').reduce((o, k) => (o && o[k] !== undefined ? o[k] : undefined), obj);
|
|
73
|
+
}
|
|
33
74
|
|
|
34
|
-
//
|
|
75
|
+
// ============================================================
|
|
76
|
+
// Checks
|
|
77
|
+
// ============================================================
|
|
35
78
|
|
|
36
79
|
async function checkNodeVersion() {
|
|
37
80
|
const spinner = ora('Checking Node.js version...').start();
|
|
38
|
-
const
|
|
39
|
-
const major = parseInt(version.substring(1).split('.')[0], 10);
|
|
81
|
+
const major = parseInt(process.version.substring(1).split('.')[0], 10);
|
|
40
82
|
|
|
41
83
|
if (major >= 18) {
|
|
42
|
-
spinner.succeed(chalk.green(`Node.js
|
|
84
|
+
spinner.succeed(chalk.green(`Node.js ${process.version}`));
|
|
85
|
+
return record('node', 'pass', `Node.js ${process.version}`);
|
|
43
86
|
} else {
|
|
44
|
-
spinner.fail(chalk.red(`Node.js ${version} is too old.
|
|
87
|
+
spinner.fail(chalk.red(`Node.js ${process.version} is too old. Install Node.js v18+.`));
|
|
88
|
+
return record('node', 'fail', `Node.js ${process.version} ā requires v18+`);
|
|
45
89
|
}
|
|
46
90
|
}
|
|
47
91
|
|
|
48
|
-
async function
|
|
49
|
-
const spinner = ora('Checking
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
async function checkGit() {
|
|
93
|
+
const spinner = ora('Checking Git...').start();
|
|
94
|
+
if (commandExists('git')) {
|
|
95
|
+
const ver = getCommandVersion('git') || 'found';
|
|
96
|
+
spinner.succeed(chalk.green(`Git: ${ver}`));
|
|
97
|
+
return record('git', 'pass', ver);
|
|
98
|
+
} else {
|
|
99
|
+
const helpUrl = `${SITE_URL}/docs/troubleshooting/spawn-git-enoent`;
|
|
100
|
+
spinner.fail(chalk.red(`Git not found. npm packages that use Git will fail.`));
|
|
101
|
+
console.log(chalk.gray(` Fix: ${helpUrl}`));
|
|
102
|
+
if (FIX_MODE && os.platform() === 'win32') {
|
|
103
|
+
console.log(chalk.yellow(' Auto-fix: running winget install Git.Git ...'));
|
|
104
|
+
try { execSync('winget install Git.Git', { stdio: 'inherit' }); } catch { /* ignore */ }
|
|
105
|
+
}
|
|
106
|
+
return record('git', 'fail', 'Git not installed or not in PATH', helpUrl);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function checkDocker() {
|
|
111
|
+
const spinner = ora('Checking Docker...').start();
|
|
112
|
+
if (commandExists('docker')) {
|
|
113
|
+
const ver = getCommandVersion('docker') || 'found';
|
|
114
|
+
spinner.succeed(chalk.green(`Docker: ${ver}`));
|
|
115
|
+
return record('docker', 'pass', ver);
|
|
116
|
+
} else {
|
|
117
|
+
const helpUrl = `${SITE_URL}/docs/troubleshooting/spawn-docker-enoent`;
|
|
118
|
+
spinner.warn(chalk.yellow('Docker not found. Agent sandbox mode will fail.'));
|
|
119
|
+
console.log(chalk.gray(` Fix: Install Docker or disable sandbox: openclaw config set agents.defaults.sandbox.mode off`));
|
|
120
|
+
console.log(chalk.gray(` Guide: ${helpUrl}`));
|
|
121
|
+
return record('docker', 'warn', 'Docker not installed (sandbox mode will fail)', helpUrl);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
52
124
|
|
|
125
|
+
async function checkConfigDir() {
|
|
126
|
+
const spinner = ora('Checking config directory...').start();
|
|
53
127
|
try {
|
|
54
|
-
await fs.promises.access(
|
|
55
|
-
spinner.succeed(chalk.green(`Config directory
|
|
56
|
-
|
|
57
|
-
|
|
128
|
+
await fs.promises.access(CONFIG_DIR);
|
|
129
|
+
spinner.succeed(chalk.green(`Config directory: ${CONFIG_DIR}`));
|
|
130
|
+
return record('config_dir', 'pass', CONFIG_DIR);
|
|
131
|
+
} catch {
|
|
132
|
+
spinner.fail(chalk.red(`Config directory missing: ${CONFIG_DIR}`));
|
|
133
|
+
console.log(chalk.gray(` Fix: Run 'openclaw init' or use ${SITE_URL}/tools/config`));
|
|
134
|
+
if (FIX_MODE) {
|
|
135
|
+
console.log(chalk.yellow(' Auto-fix: creating directory...'));
|
|
136
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
137
|
+
console.log(chalk.green(' Created.'));
|
|
138
|
+
}
|
|
139
|
+
return record('config_dir', 'fail', `Missing: ${CONFIG_DIR}`);
|
|
58
140
|
}
|
|
59
141
|
}
|
|
60
142
|
|
|
61
143
|
async function checkConfigFile() {
|
|
62
|
-
const spinner = ora('Checking
|
|
63
|
-
|
|
64
|
-
|
|
144
|
+
const spinner = ora('Checking config file (clawhub.json)...').start();
|
|
145
|
+
try {
|
|
146
|
+
await fs.promises.access(CONFIG_FILE);
|
|
147
|
+
} catch {
|
|
148
|
+
const helpUrl = `${SITE_URL}/tools/config`;
|
|
149
|
+
spinner.fail(chalk.yellow('Config file missing (clawhub.json).'));
|
|
150
|
+
console.log(chalk.gray(` Generate one: ${helpUrl}`));
|
|
151
|
+
return record('config_file', 'fail', 'clawhub.json not found', helpUrl);
|
|
152
|
+
}
|
|
65
153
|
|
|
154
|
+
// Validate JSON syntax
|
|
66
155
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
156
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
157
|
+
JSON.parse(raw);
|
|
158
|
+
spinner.succeed(chalk.green('Config file found and valid JSON.'));
|
|
159
|
+
return record('config_file', 'pass', 'clawhub.json valid');
|
|
160
|
+
} catch (e) {
|
|
161
|
+
const helpUrl = `${SITE_URL}/docs/troubleshooting/json-parse-errors`;
|
|
162
|
+
spinner.fail(chalk.red(`Config file has invalid JSON: ${e.message}`));
|
|
163
|
+
console.log(chalk.gray(` Fix: ${helpUrl}`));
|
|
164
|
+
return record('config_file', 'fail', `Invalid JSON: ${e.message}`, helpUrl);
|
|
71
165
|
}
|
|
72
166
|
}
|
|
73
167
|
|
|
74
168
|
async function checkPermissions() {
|
|
75
|
-
const spinner = ora('Checking
|
|
76
|
-
const homeDir = os.homedir();
|
|
77
|
-
const configPath = path.join(homeDir, '.openclaw');
|
|
78
|
-
|
|
169
|
+
const spinner = ora('Checking directory permissions...').start();
|
|
79
170
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
spinner.succeed(chalk.green('Write permission OK for ~/.openclaw'));
|
|
84
|
-
} else {
|
|
85
|
-
spinner.info(chalk.gray('Skipping permission check (directory missing).'));
|
|
171
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
172
|
+
spinner.info(chalk.gray('Skipped (directory missing).'));
|
|
173
|
+
return record('permissions', 'skip', 'Directory missing');
|
|
86
174
|
}
|
|
175
|
+
await fs.promises.access(CONFIG_DIR, fs.constants.W_OK);
|
|
176
|
+
spinner.succeed(chalk.green('Write permission OK for ~/.openclaw'));
|
|
177
|
+
return record('permissions', 'pass', 'Write permission OK');
|
|
178
|
+
} catch {
|
|
179
|
+
spinner.fail(chalk.red('No write permission for ~/.openclaw'));
|
|
180
|
+
console.log(chalk.gray(' Fix: sudo chown -R $(whoami) ~/.openclaw'));
|
|
181
|
+
if (FIX_MODE && os.platform() !== 'win32') {
|
|
182
|
+
console.log(chalk.yellow(' Auto-fix: fixing ownership...'));
|
|
183
|
+
try { execSync(`chown -R $(whoami) "${CONFIG_DIR}"`, { stdio: 'inherit' }); } catch { /* ignore */ }
|
|
184
|
+
}
|
|
185
|
+
return record('permissions', 'fail', 'No write permission');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function checkGatewayTokens() {
|
|
190
|
+
const spinner = ora('Checking gateway token alignment...').start();
|
|
191
|
+
const config = readConfig();
|
|
192
|
+
if (!config) {
|
|
193
|
+
spinner.info(chalk.gray('Skipped (no valid config).'));
|
|
194
|
+
return record('tokens', 'skip', 'No config to check');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const authToken = getNestedValue(config, 'gateway.auth.token');
|
|
198
|
+
const remoteToken = getNestedValue(config, 'gateway.remote.token');
|
|
199
|
+
const envToken = process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
200
|
+
|
|
201
|
+
if (!authToken && !remoteToken) {
|
|
202
|
+
spinner.info(chalk.gray('No gateway tokens configured (will auto-generate).'));
|
|
203
|
+
return record('tokens', 'info', 'No tokens configured');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check env override
|
|
207
|
+
if (envToken && authToken && envToken !== authToken) {
|
|
208
|
+
const helpUrl = `${SITE_URL}/docs/troubleshooting/gateway-token-mismatch`;
|
|
209
|
+
spinner.fail(chalk.red('OPENCLAW_GATEWAY_TOKEN env var overrides config! Tokens will mismatch.'));
|
|
210
|
+
console.log(chalk.gray(` Env: ${envToken.substring(0, 8)}... Config: ${authToken.substring(0, 8)}...`));
|
|
211
|
+
console.log(chalk.gray(` Fix: ${helpUrl}`));
|
|
212
|
+
return record('tokens', 'fail', 'Env var overrides gateway.auth.token', helpUrl);
|
|
213
|
+
}
|
|
87
214
|
|
|
88
|
-
|
|
89
|
-
|
|
215
|
+
if (authToken && remoteToken && authToken !== remoteToken) {
|
|
216
|
+
const helpUrl = `${SITE_URL}/docs/troubleshooting/gateway-token-mismatch`;
|
|
217
|
+
spinner.fail(chalk.red('gateway.auth.token ā gateway.remote.token ā clients cannot connect.'));
|
|
218
|
+
console.log(chalk.gray(` Auth: ${authToken.substring(0, 8)}...`));
|
|
219
|
+
console.log(chalk.gray(` Remote: ${remoteToken.substring(0, 8)}...`));
|
|
220
|
+
console.log(chalk.gray(` Fix: ${helpUrl}`));
|
|
221
|
+
if (FIX_MODE) {
|
|
222
|
+
console.log(chalk.yellow(' Auto-fix: aligning remote token to match auth token...'));
|
|
223
|
+
config.gateway.remote.token = authToken;
|
|
224
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
225
|
+
console.log(chalk.green(' Fixed. Restart gateway: openclaw gateway restart'));
|
|
226
|
+
}
|
|
227
|
+
return record('tokens', 'fail', 'auth.token ā remote.token', helpUrl);
|
|
90
228
|
}
|
|
229
|
+
|
|
230
|
+
spinner.succeed(chalk.green('Gateway tokens aligned.'));
|
|
231
|
+
return record('tokens', 'pass', 'Tokens aligned');
|
|
91
232
|
}
|
|
92
233
|
|
|
93
|
-
async function
|
|
94
|
-
const spinner = ora('Checking
|
|
234
|
+
async function checkStaleLocks() {
|
|
235
|
+
const spinner = ora('Checking for stale lock files...').start();
|
|
236
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
237
|
+
spinner.info(chalk.gray('No sessions directory.'));
|
|
238
|
+
return record('locks', 'skip', 'No sessions directory');
|
|
239
|
+
}
|
|
95
240
|
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
241
|
+
try {
|
|
242
|
+
const files = fs.readdirSync(SESSIONS_DIR).filter(f => f.endsWith('.lock'));
|
|
243
|
+
if (files.length === 0) {
|
|
244
|
+
spinner.succeed(chalk.green('No stale lock files.'));
|
|
245
|
+
return record('locks', 'pass', 'No lock files');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const helpUrl = `${SITE_URL}/docs/troubleshooting/gateway-lock-timeout`;
|
|
249
|
+
spinner.warn(chalk.yellow(`Found ${files.length} lock file(s): ${files.join(', ')}`));
|
|
250
|
+
console.log(chalk.gray(` This may cause "gateway already running" errors.`));
|
|
251
|
+
console.log(chalk.gray(` Fix: ${helpUrl}`));
|
|
101
252
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
spinner.info(chalk.gray(`Network check skipped: ${e.message}`));
|
|
253
|
+
if (FIX_MODE) {
|
|
254
|
+
console.log(chalk.yellow(' Auto-fix: removing stale locks...'));
|
|
255
|
+
for (const f of files) {
|
|
256
|
+
fs.unlinkSync(path.join(SESSIONS_DIR, f));
|
|
107
257
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
258
|
+
console.log(chalk.green(` Removed ${files.length} lock file(s).`));
|
|
259
|
+
}
|
|
260
|
+
return record('locks', 'warn', `${files.length} lock file(s) found`, helpUrl);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
spinner.info(chalk.gray(`Could not check locks: ${e.message}`));
|
|
263
|
+
return record('locks', 'skip', e.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
110
266
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
267
|
+
async function checkGatewayPort() {
|
|
268
|
+
const spinner = ora('Checking Gateway port (18789)...').start();
|
|
269
|
+
const inUse = await checkPort(18789);
|
|
270
|
+
if (inUse) {
|
|
271
|
+
spinner.succeed(chalk.green('Gateway port 18789 is in use (gateway likely running).'));
|
|
272
|
+
return record('gateway_port', 'pass', 'Port 18789 in use (gateway running)');
|
|
273
|
+
} else {
|
|
274
|
+
spinner.info(chalk.gray('Gateway port 18789 is free (gateway not running).'));
|
|
275
|
+
return record('gateway_port', 'info', 'Port 18789 free (gateway not running)');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function checkAgentPort() {
|
|
280
|
+
const spinner = ora('Checking Agent port (3000)...').start();
|
|
281
|
+
const inUse = await checkPort(3000);
|
|
282
|
+
if (inUse) {
|
|
283
|
+
spinner.succeed(chalk.green('Agent port 3000 is in use (agent likely running).'));
|
|
284
|
+
return record('agent_port', 'pass', 'Port 3000 in use');
|
|
285
|
+
} else {
|
|
286
|
+
spinner.info(chalk.gray('Agent port 3000 is free (agent not running).'));
|
|
287
|
+
return record('agent_port', 'info', 'Port 3000 free');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function checkCDPPort() {
|
|
292
|
+
const spinner = ora('Checking Browser CDP port (18800)...').start();
|
|
293
|
+
const inUse = await checkPort(18800);
|
|
294
|
+
if (inUse) {
|
|
295
|
+
spinner.info(chalk.gray('CDP port 18800 in use (browser control active or stray process).'));
|
|
296
|
+
return record('cdp_port', 'info', 'Port 18800 in use');
|
|
297
|
+
} else {
|
|
298
|
+
spinner.succeed(chalk.green('CDP port 18800 is free.'));
|
|
299
|
+
return record('cdp_port', 'pass', 'Port 18800 free');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================================
|
|
304
|
+
// Report Generator
|
|
305
|
+
// ============================================================
|
|
306
|
+
|
|
307
|
+
function generateReportUrl() {
|
|
308
|
+
const compressed = JSON.stringify({
|
|
309
|
+
t: report.ts,
|
|
310
|
+
o: report.os,
|
|
311
|
+
n: report.node,
|
|
312
|
+
f: report.fix,
|
|
313
|
+
c: report.checks.map(c => [c.id, c.status[0], c.message.substring(0, 80)]),
|
|
117
314
|
});
|
|
315
|
+
const encoded = Buffer.from(compressed).toString('base64url');
|
|
316
|
+
return `${SITE_URL}/tools/doctor?r=${encoded}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================
|
|
320
|
+
// Main
|
|
321
|
+
// ============================================================
|
|
322
|
+
|
|
323
|
+
const steps = [
|
|
324
|
+
checkNodeVersion,
|
|
325
|
+
checkGit,
|
|
326
|
+
checkDocker,
|
|
327
|
+
checkConfigDir,
|
|
328
|
+
checkConfigFile,
|
|
329
|
+
checkPermissions,
|
|
330
|
+
checkGatewayTokens,
|
|
331
|
+
checkStaleLocks,
|
|
332
|
+
checkGatewayPort,
|
|
333
|
+
checkAgentPort,
|
|
334
|
+
checkCDPPort,
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
async function run() {
|
|
338
|
+
console.clear();
|
|
339
|
+
console.log(chalk.cyan.bold('\nš¦ ClawKit Doctor v2.0\n'));
|
|
340
|
+
|
|
341
|
+
if (FIX_MODE) {
|
|
342
|
+
console.log(chalk.yellow.bold('ā” Fix mode enabled ā will auto-repair issues where possible.\n'));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const step of steps) {
|
|
346
|
+
await step();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Summary
|
|
350
|
+
const fails = report.checks.filter(c => c.status === 'fail');
|
|
351
|
+
const warns = report.checks.filter(c => c.status === 'warn');
|
|
352
|
+
const passes = report.checks.filter(c => c.status === 'pass');
|
|
353
|
+
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(chalk.bold('ā'.repeat(50)));
|
|
356
|
+
|
|
357
|
+
if (fails.length === 0 && warns.length === 0) {
|
|
358
|
+
console.log(chalk.green.bold('\nā
All checks passed! Your environment looks healthy.\n'));
|
|
359
|
+
} else {
|
|
360
|
+
console.log(chalk.yellow.bold(`\nš Summary: ${passes.length} passed, ${warns.length} warnings, ${fails.length} failed\n`));
|
|
361
|
+
|
|
362
|
+
if (fails.length > 0) {
|
|
363
|
+
console.log(chalk.red.bold(' Issues to fix:'));
|
|
364
|
+
for (const f of fails) {
|
|
365
|
+
console.log(chalk.red(` ā ${f.message}`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (warns.length > 0) {
|
|
369
|
+
console.log(chalk.yellow.bold(' Warnings:'));
|
|
370
|
+
for (const w of warns) {
|
|
371
|
+
console.log(chalk.yellow(` ā ${w.message}`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!FIX_MODE && fails.length > 0) {
|
|
376
|
+
console.log(chalk.cyan(`\n š” Run with --fix to auto-repair: npx clawkit-doctor@latest --fix\n`));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Report URL
|
|
381
|
+
const reportUrl = generateReportUrl();
|
|
382
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
383
|
+
console.log(chalk.cyan.bold('\nš Share this report:'));
|
|
384
|
+
console.log(chalk.white(` ${reportUrl}\n`));
|
|
385
|
+
console.log(chalk.gray(`For full troubleshooting guides: ${SITE_URL}/docs/troubleshooting\n`));
|
|
118
386
|
}
|
|
387
|
+
|
|
388
|
+
run().catch(err => {
|
|
389
|
+
console.error(chalk.red('\nā Unexpected Error:'), err.message);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawkit-doctor",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Diagnostic tool for OpenClaw environment",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Diagnostic and auto-fix tool for OpenClaw environment",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"clawkit-doctor": "
|
|
7
|
+
"clawkit-doctor": "bin/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
"chalk": "^4.1.2",
|
|
22
22
|
"ora": "^5.4.1"
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|