clawkit-doctor 1.0.0 → 2.0.1

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