mpx-scan 1.3.0 โ†’ 1.3.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.
package/README.md CHANGED
@@ -1,26 +1,25 @@
1
1
  # mpx-scan ๐Ÿ”
2
2
 
3
- **Professional website security scanner for developers and AI agents**
3
+ **Professional website security scanner for developers and AI agents.**
4
4
 
5
5
  Check your site's security headers, SSL/TLS configuration, DNS settings, and get actionable fix suggestions โ€” all from your terminal.
6
6
 
7
7
  Part of the [Mesaplex](https://mesaplex.com) developer toolchain.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/mpx-scan.svg)](https://www.npmjs.com/package/mpx-scan)
10
- [![License](https://img.shields.io/badge/license-Dual-blue.svg)](LICENSE)
10
+ [![License: Dual](https://img.shields.io/badge/license-Dual-blue.svg)](LICENSE)
11
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
11
12
 
12
- ## โœจ Features
13
+ ## Features
13
14
 
14
15
  - **Zero-config security scanning** โ€” just point it at a URL
15
16
  - **Beautiful terminal output** with color-coded results
16
17
  - **Structured JSON output** โ€” `--json` for CI/CD and AI agent consumption
17
- - **MCP server** โ€” integrates with any MCP-compatible AI agent (Claude, GPT, Cursor, etc.)
18
+ - **MCP server** โ€” integrates with any MCP-compatible AI agent (Claude, Cursor, Windsurf, etc.)
18
19
  - **Actionable fix suggestions** โ€” copy-paste config for nginx, Apache, Caddy, Cloudflare
19
20
  - **Batch scanning** โ€” pipe URLs from stdin
20
21
  - **Self-documenting** โ€” `--schema` returns machine-readable tool description
21
- - **Fast** โ€” scans complete in seconds
22
22
  - **Zero native dependencies** โ€” installs cleanly everywhere
23
- - **CI/CD ready** โ€” predictable exit codes and JSON output
24
23
 
25
24
  ### Security Checks
26
25
 
@@ -30,23 +29,42 @@ Part of the [Mesaplex](https://mesaplex.com) developer toolchain.
30
29
  - โœ… Server information leakage
31
30
  - โœ… CORS misconfiguration
32
31
  - โœ… Mixed content detection
33
- - โœ… DNS security (DNSSEC, CAA records) โ€” *Pro only*
34
- - โœ… Subresource Integrity (SRI) โ€” *Pro only*
35
- - โœ… Open redirect detection โ€” *Pro only*
36
- - โœ… Exposed sensitive files โ€” *Pro only*
32
+ - โœ… DNS security (DNSSEC, CAA records) โ€” *Pro*
33
+ - โœ… Subresource Integrity (SRI) โ€” *Pro*
34
+ - โœ… Open redirect detection โ€” *Pro*
35
+ - โœ… Exposed sensitive files โ€” *Pro*
37
36
 
38
- ## ๐Ÿš€ Quick Start
37
+ ## Installation
38
+
39
+ ```bash
40
+ npm install -g mpx-scan
41
+ ```
42
+
43
+ Or run directly with npx:
39
44
 
40
45
  ```bash
41
- # Run once without installing
42
46
  npx mpx-scan https://example.com
47
+ ```
43
48
 
44
- # Or install globally
45
- npm install -g mpx-scan
49
+ **Requirements:** Node.js 18+ ยท No native dependencies ยท macOS, Linux, Windows
50
+
51
+ ## Quick Start
52
+
53
+ ```bash
54
+ # Basic scan
46
55
  mpx-scan https://example.com
56
+
57
+ # JSON output
58
+ mpx-scan https://example.com --json
59
+
60
+ # Fix suggestions for nginx
61
+ mpx-scan https://example.com --fix nginx
62
+
63
+ # Deep scan (Pro)
64
+ mpx-scan https://example.com --full
47
65
  ```
48
66
 
49
- ## ๐Ÿ“– Usage
67
+ ## Usage
50
68
 
51
69
  ### Basic Scan
52
70
 
@@ -65,7 +83,7 @@ Returns structured JSON to stdout (progress/status goes to stderr):
65
83
  ```json
66
84
  {
67
85
  "mpxScan": {
68
- "version": "1.1.0",
86
+ "version": "1.3.0",
69
87
  "scannedAt": "2026-02-16T22:00:00.000Z",
70
88
  "scanDuration": 350
71
89
  },
@@ -90,7 +108,7 @@ Returns structured JSON to stdout (progress/status goes to stderr):
90
108
  }
91
109
  ```
92
110
 
93
- ### Get Fix Suggestions
111
+ ### Fix Suggestions
94
112
 
95
113
  ```bash
96
114
  mpx-scan https://example.com --fix nginx
@@ -99,18 +117,27 @@ mpx-scan https://example.com --fix caddy
99
117
  mpx-scan https://example.com --fix cloudflare
100
118
  ```
101
119
 
102
- ### Deep Scan (Pro)
120
+ ### Brief Output
103
121
 
104
122
  ```bash
105
- mpx-scan https://example.com --full
123
+ mpx-scan https://example.com --brief
106
124
  ```
107
125
 
108
- ### Brief Output
126
+ ### PDF Export
109
127
 
110
128
  ```bash
111
- mpx-scan https://example.com --brief
129
+ # Generate PDF report (auto-named)
130
+ mpx-scan https://example.com --pdf
131
+
132
+ # Specify output filename
133
+ mpx-scan https://example.com --pdf report.pdf
134
+
135
+ # Combine with JSON output
136
+ mpx-scan https://example.com --json --pdf report.pdf
112
137
  ```
113
138
 
139
+ Generates a professional PDF report with color-coded findings, severity grades, and actionable recommendations.
140
+
114
141
  ### Batch Scanning
115
142
 
116
143
  ```bash
@@ -127,7 +154,38 @@ mpx-scan --schema
127
154
 
128
155
  Returns a JSON schema describing all commands, flags, inputs, and outputs โ€” designed for AI agent tool discovery.
129
156
 
130
- ## ๐Ÿค– AI Agent Usage
157
+ ### CLI Reference
158
+
159
+ ```
160
+ Usage: mpx-scan [url] [options]
161
+
162
+ Arguments:
163
+ url URL to scan
164
+
165
+ Options:
166
+ -V, --version Output version number
167
+ --json Output as structured JSON
168
+ --full Run all checks (Pro only)
169
+ --brief Brief one-line output
170
+ --quiet, -q Minimal output (no banners)
171
+ --no-color Disable ANSI color codes
172
+ --batch Read URLs from stdin (one per line)
173
+ --schema Output JSON schema for tool discovery
174
+ --pdf [filename] Export results as a PDF report
175
+ --fix <platform> Generate fix config (nginx, apache, caddy, cloudflare)
176
+ --timeout <seconds> Connection timeout (default: 10)
177
+ --ci CI mode: exit 1 if below --min-score
178
+ --min-score <score> Minimum score for CI mode (default: 70)
179
+ -h, --help Display help
180
+
181
+ Commands:
182
+ license Show license status
183
+ activate <key> Activate Pro license
184
+ deactivate Return to free tier
185
+ mcp Start MCP stdio server
186
+ ```
187
+
188
+ ## AI Agent Usage
131
189
 
132
190
  mpx-scan is designed to be used by AI agents as well as humans.
133
191
 
@@ -151,36 +209,16 @@ The MCP server exposes these tools:
151
209
  - **`generate_fixes`** โ€” Scan and generate platform-specific fix config
152
210
  - **`get_schema`** โ€” Get full tool schema
153
211
 
154
- ### Programmatic Usage
155
-
156
- ```bash
157
- # JSON output for parsing
158
- mpx-scan https://example.com --json
159
-
160
- # Batch processing
161
- cat urls.txt | mpx-scan --batch --json
162
-
163
- # Schema discovery
164
- mpx-scan --schema
165
-
166
- # Quiet mode (no banners, progress goes to stderr)
167
- mpx-scan https://example.com --json --quiet
168
- ```
169
-
170
212
  ### Exit Codes
171
213
 
172
214
  | Code | Meaning |
173
215
  |------|---------|
174
- | 0 | Scan complete, no security issues found |
175
- | 1 | Scan complete, security issues found |
176
- | 2 | Invalid arguments |
177
- | 3 | Configuration error (license, rate limit) |
178
- | 4 | Network/connectivity error |
216
+ | 0 | Success, no issues found |
217
+ | 1 | Issues found or error |
218
+ | 2 | Invalid usage or bad arguments |
179
219
 
180
220
  ### Error Responses (JSON mode)
181
221
 
182
- When `--json` is used, errors return structured JSON:
183
-
184
222
  ```json
185
223
  {
186
224
  "error": "Description of what went wrong",
@@ -193,14 +231,11 @@ Error codes: `ERR_NETWORK`, `ERR_SCAN`, `ERR_RATE_LIMIT`, `ERR_PRO_REQUIRED`, `E
193
231
  ### Automation Tips
194
232
 
195
233
  - Use `--json` for machine-parseable output (stdout only, no ANSI)
196
- - Use `--no-color` to strip ANSI codes from human-readable output
197
234
  - Use `--quiet` to suppress banners and progress info
198
- - Pipe `--batch --json` for JSONL (one result per line) processing
235
+ - Use `--batch --json` for JSONL processing
199
236
  - Check exit codes for pass/fail decisions in CI/CD
200
237
 
201
- ## ๐ŸŽฏ Use Cases
202
-
203
- ### CI/CD Integration
238
+ ## CI/CD Integration
204
239
 
205
240
  ```yaml
206
241
  # .github/workflows/security.yml
@@ -213,38 +248,25 @@ jobs:
213
248
  - run: npx mpx-scan https://mysite.com --ci --min-score 70 --json
214
249
  ```
215
250
 
216
- ### Monitoring Script
217
-
218
- ```bash
219
- #!/bin/bash
220
- for site in site1.com site2.com site3.com; do
221
- result=$(npx mpx-scan "$site" --json 2>/dev/null)
222
- grade=$(echo "$result" | jq -r '.score.grade')
223
- echo "$site: $grade"
224
- done
225
- ```
226
-
227
- ## ๐Ÿ“Š Free vs Pro
251
+ ## Free vs Pro
228
252
 
229
253
  | Feature | Free | Pro |
230
254
  |---------|------|-----|
231
- | **Daily scans** | 3 | Unlimited |
232
- | **Security headers** | โœ… | โœ… |
233
- | **SSL/TLS checks** | โœ… | โœ… |
234
- | **Server info checks** | โœ… | โœ… |
235
- | **JSON output** | โœ… | โœ… |
236
- | **Batch scanning** | โœ… | โœ… |
237
- | **MCP server** | โœ… | โœ… |
238
- | **DNS security** | โŒ | โœ… |
239
- | **Cookie security** | โŒ | โœ… |
240
- | **SRI checks** | โŒ | โœ… |
241
- | **Exposed files** | โŒ | โœ… |
242
- | **Mixed content** | โŒ | โœ… |
243
- | **Full scan (--full)** | โŒ | โœ… |
244
-
245
- **Upgrade to Pro:** [https://mesaplex.com/mpx-scan](https://mesaplex.com/mpx-scan)
246
-
247
- ## ๐Ÿ” License Management
255
+ | Daily scans | 3 | Unlimited |
256
+ | Security headers | โœ… | โœ… |
257
+ | SSL/TLS checks | โœ… | โœ… |
258
+ | Server info checks | โœ… | โœ… |
259
+ | JSON output | โœ… | โœ… |
260
+ | Batch scanning | โœ… | โœ… |
261
+ | MCP server | โœ… | โœ… |
262
+ | DNS security | โŒ | โœ… |
263
+ | Cookie security | โŒ | โœ… |
264
+ | SRI checks | โŒ | โœ… |
265
+ | Exposed files | โŒ | โœ… |
266
+ | Mixed content | โŒ | โœ… |
267
+ | Full scan (`--full`) | โŒ | โœ… |
268
+
269
+ ### License Management
248
270
 
249
271
  ```bash
250
272
  mpx-scan license # Check status
@@ -252,83 +274,24 @@ mpx-scan activate MPX-PRO-XXXXXXXX # Activate Pro
252
274
  mpx-scan deactivate # Return to free tier
253
275
  ```
254
276
 
255
- ## ๐Ÿ› ๏ธ CLI Reference
256
-
257
- ```
258
- Usage: mpx-scan [url] [options]
259
-
260
- Arguments:
261
- url URL to scan
262
-
263
- Options:
264
- -V, --version Output version number
265
- --json Output as structured JSON
266
- --full Run all checks (Pro only)
267
- --brief Brief one-line output
268
- --quiet, -q Minimal output (no banners)
269
- --no-color Disable ANSI color codes
270
- --batch Read URLs from stdin (one per line)
271
- --schema Output JSON schema for tool discovery
272
- --fix <platform> Generate fix config (nginx, apache, caddy, cloudflare)
273
- --timeout <seconds> Connection timeout (default: 10)
274
- --ci CI mode: exit 1 if below --min-score
275
- --min-score <score> Minimum score for CI mode (default: 70)
276
- -h, --help Display help
277
-
278
- Commands:
279
- license Show license status
280
- activate <key> Activate Pro license
281
- deactivate Return to free tier
282
- mcp Start MCP stdio server
283
- ```
284
-
285
- ## ๐Ÿ“ฆ Installation
286
-
287
- ```bash
288
- # Global
289
- npm install -g mpx-scan
290
-
291
- # Project dependency
292
- npm install --save-dev mpx-scan
293
-
294
- # One-off with npx
295
- npx mpx-scan https://example.com
296
- ```
297
-
298
- ### Requirements
299
-
300
- - Node.js 18.0.0 or higher
301
- - No native dependencies
302
- - Works on macOS, Linux, Windows
303
-
304
- ## ๐Ÿงช Testing
305
-
306
- ```bash
307
- npm test
308
- ```
309
-
310
- ## ๐Ÿค Contributing
311
-
312
- Security improvements and bug fixes are welcome!
313
-
314
- ## ๐Ÿ“„ License
277
+ **Upgrade to Pro:** [https://mesaplex.com/mpx-scan](https://mesaplex.com/mpx-scan)
315
278
 
316
- Dual License: Free tier for personal use, Pro license for commercial use and advanced features.
279
+ ## License
317
280
 
318
- See [LICENSE](LICENSE) for full terms.
281
+ Dual License โ€” Free tier for personal use, Pro license for commercial use and advanced features. See [LICENSE](LICENSE) for full terms.
319
282
 
320
- ## ๐Ÿ”— Links
283
+ ## Links
321
284
 
322
- - **Website:** [https://mesaplex.com/mpx-scan](https://mesaplex.com/mpx-scan)
285
+ - **Website:** [https://mesaplex.com](https://mesaplex.com)
323
286
  - **npm:** [https://www.npmjs.com/package/mpx-scan](https://www.npmjs.com/package/mpx-scan)
324
287
  - **GitHub:** [https://github.com/mesaplexdev/mpx-scan](https://github.com/mesaplexdev/mpx-scan)
325
288
  - **Support:** support@mesaplex.com
326
289
 
327
- ## ๐Ÿ“š Related Tools
290
+ ### Related Tools
328
291
 
329
- - **mpx-scan** โ€” Security scanner (you are here)
330
- - **[mpx-api](https://www.npmjs.com/package/mpx-api)** โ€” API testing toolkit
331
- - **[mpx-db](https://www.npmjs.com/package/mpx-db)** โ€” Database toolkit
292
+ - **[mpx-api](https://www.npmjs.com/package/mpx-api)** โ€” API testing, mocking, and documentation
293
+ - **[mpx-db](https://www.npmjs.com/package/mpx-db)** โ€” Database management CLI
294
+ - **[mpx-secrets-audit](https://www.npmjs.com/package/mpx-secrets-audit)** โ€” Secret lifecycle tracking and audit
332
295
 
333
296
  ---
334
297
 
package/bin/cli.js CHANGED
@@ -12,6 +12,7 @@ const chalk = require('chalk');
12
12
  const { scan } = require('../src/index');
13
13
  const { formatReport, formatBrief } = require('../src/reporters/terminal');
14
14
  const { formatJSON } = require('../src/reporters/json');
15
+ const { generatePDF, getDefaultPDFFilename } = require('../src/reporters/pdf');
15
16
  const { generateFixes, PLATFORMS } = require('../src/generators/fixes');
16
17
  const { getSchema } = require('../src/schema');
17
18
  const {
@@ -25,13 +26,13 @@ const {
25
26
 
26
27
  const pkg = require('../package.json');
27
28
 
28
- // Exit codes per AI-native spec
29
+ // Exit codes
29
30
  const EXIT = {
30
31
  SUCCESS: 0, // Success, no issues found
31
- ISSUES_FOUND: 1, // Success, issues found
32
- BAD_ARGS: 2, // Invalid arguments
33
- CONFIG_ERROR: 3, // Configuration error
34
- NETWORK_ERROR: 4 // Network/connectivity error
32
+ ISSUES_FOUND: 1, // Issues found or error occurred
33
+ BAD_ARGS: 2, // Invalid arguments / bad usage
34
+ CONFIG_ERROR: 1, // Configuration error
35
+ NETWORK_ERROR: 1 // Network/connectivity error
35
36
  };
36
37
 
37
38
  // Auto-detect non-interactive mode
@@ -39,6 +40,12 @@ const isInteractive = process.stdout.isTTY && !process.env.CI;
39
40
 
40
41
  const program = new Command();
41
42
 
43
+ // Error handling โ€” set before any command/option registration
44
+ program.exitOverride();
45
+ program.configureOutput({
46
+ writeErr: () => {} // Suppress Commander's own error output; we handle it in the catch below
47
+ });
48
+
42
49
  program
43
50
  .name('mpx-scan')
44
51
  .description('Professional website security scanner โ€” check headers, SSL, DNS, and more')
@@ -52,6 +59,7 @@ program
52
59
  .option('--batch', 'Batch mode: read URLs from stdin (one per line)')
53
60
  .option('--schema', 'Output JSON schema describing all commands and flags')
54
61
  .option('--fix <platform>', `Generate fix config for platform (${PLATFORMS.join(', ')})`)
62
+ .option('--pdf [filename]', 'Export results as a PDF report')
55
63
  .option('--timeout <seconds>', 'Connection timeout', '10')
56
64
  .option('--ci', 'CI/CD mode: exit 1 if score below threshold')
57
65
  .option('--min-score <score>', 'Minimum score for CI mode', '70')
@@ -71,7 +79,8 @@ program
71
79
 
72
80
  // Show help if no URL provided
73
81
  if (!url) {
74
- program.help();
82
+ program.outputHelp();
83
+ process.exit(EXIT.BAD_ARGS);
75
84
  return;
76
85
  }
77
86
 
@@ -95,7 +104,7 @@ async function runSingleScan(url, options) {
95
104
  if (jsonMode) {
96
105
  console.log(JSON.stringify({ error: `Invalid platform: "${options.fix}". Valid platforms: ${PLATFORMS.join(', ')}`, code: 'ERR_BAD_ARGS' }, null, 2));
97
106
  } else {
98
- console.error(chalk.red.bold(`\nโŒ Invalid platform: "${options.fix}"`));
107
+ console.error(chalk.red(`Error: Invalid platform: "${options.fix}"`));
99
108
  console.error(chalk.yellow(`Valid platforms: ${PLATFORMS.join(', ')}`));
100
109
  console.error('');
101
110
  }
@@ -109,7 +118,7 @@ async function runSingleScan(url, options) {
109
118
  if (jsonMode) {
110
119
  console.log(JSON.stringify({ error: 'Invalid --timeout value. Must be a non-negative number.', code: 'ERR_BAD_ARGS' }, null, 2));
111
120
  } else {
112
- console.error(chalk.red.bold('\nโŒ Invalid --timeout value. Must be a non-negative number.'));
121
+ console.error(chalk.red('Error: Invalid --timeout value. Must be a non-negative number.'));
113
122
  }
114
123
  return EXIT.BAD_ARGS;
115
124
  }
@@ -129,7 +138,7 @@ async function runSingleScan(url, options) {
129
138
  upgrade: 'https://mesaplex.com/mpx-scan'
130
139
  }, null, 2));
131
140
  } else {
132
- console.error(chalk.red.bold('\nโŒ Daily scan limit reached'));
141
+ console.error(chalk.red('Error: Daily scan limit reached'));
133
142
  console.error(chalk.yellow(`Free tier: ${FREE_DAILY_LIMIT} scans/day`));
134
143
  console.error(chalk.gray(`Resets: ${new Date(rateLimit.resetsAt).toLocaleString()}\n`));
135
144
  console.error(chalk.blue('Upgrade to Pro for unlimited scans:'));
@@ -147,7 +156,7 @@ async function runSingleScan(url, options) {
147
156
  upgrade: 'https://mesaplex.com/mpx-scan'
148
157
  }, null, 2));
149
158
  } else {
150
- console.error(chalk.red.bold('\nโŒ --full flag requires Pro license'));
159
+ console.error(chalk.red('Error: --full flag requires Pro license'));
151
160
  console.error(chalk.yellow('Free tier includes: headers, SSL, server checks'));
152
161
  console.error(chalk.yellow('Pro includes: all checks (DNS, cookies, SRI, exposed files, etc.)\n'));
153
162
  console.error(chalk.blue('Upgrade: https://mesaplex.com/mpx-scan\n'));
@@ -184,6 +193,25 @@ async function runSingleScan(url, options) {
184
193
  console.log(formatReport(results, { ...options, quiet: quietMode }));
185
194
  }
186
195
 
196
+ // Generate PDF if requested
197
+ if (options.pdf !== undefined) {
198
+ const pdfPath = (typeof options.pdf === 'string' && options.pdf)
199
+ ? options.pdf
200
+ : getDefaultPDFFilename(results.hostname);
201
+ try {
202
+ await generatePDF(results, pdfPath);
203
+ if (!jsonMode && !options.brief) {
204
+ console.error(chalk.green(`๐Ÿ“„ PDF report saved: ${pdfPath}`));
205
+ }
206
+ } catch (pdfErr) {
207
+ if (jsonMode) {
208
+ console.error(JSON.stringify({ warning: `PDF generation failed: ${pdfErr.message}` }));
209
+ } else {
210
+ console.error(chalk.yellow(`Warning: PDF generation failed: ${pdfErr.message}`));
211
+ }
212
+ }
213
+ }
214
+
187
215
  // Check if core scanners errored with network issues (DNS failure, connection refused, etc.)
188
216
  const coreScanners = ['headers', 'ssl'];
189
217
  const coreErrored = coreScanners.every(name => {
@@ -203,7 +231,7 @@ async function runSingleScan(url, options) {
203
231
  if (jsonMode) {
204
232
  console.log(JSON.stringify({ error: 'Invalid --min-score value', code: 'ERR_BAD_ARGS' }, null, 2));
205
233
  } else {
206
- console.error(chalk.red.bold('\nโŒ Invalid --min-score value. Must be a number.'));
234
+ console.error(chalk.red('Error: Invalid --min-score value. Must be a number.'));
207
235
  }
208
236
  return EXIT.BAD_ARGS;
209
237
  }
@@ -228,7 +256,7 @@ async function runSingleScan(url, options) {
228
256
  const code = isNetworkError(err) ? 'ERR_NETWORK' : 'ERR_SCAN';
229
257
  console.log(JSON.stringify({ error: err.message, code }, null, 2));
230
258
  } else {
231
- console.error(chalk.red.bold('\nโŒ Error:'), err.message);
259
+ console.error(chalk.red('Error:'), err.message);
232
260
  console.error('');
233
261
  }
234
262
  return isNetworkError(err) ? EXIT.NETWORK_ERROR : EXIT.ISSUES_FOUND;
@@ -244,7 +272,7 @@ async function runBatchMode(options) {
244
272
  if (jsonMode) {
245
273
  console.log(JSON.stringify({ error: 'No URLs provided on stdin', code: 'ERR_NO_INPUT' }, null, 2));
246
274
  } else {
247
- console.error(chalk.red('No URLs provided. Pipe URLs via stdin:'));
275
+ console.error(chalk.red('Error: No URLs provided. Pipe URLs via stdin:'));
248
276
  console.error(chalk.gray(' cat urls.txt | mpx-scan --batch --json'));
249
277
  }
250
278
  process.exit(EXIT.BAD_ARGS);
@@ -257,7 +285,7 @@ async function runBatchMode(options) {
257
285
  if (jsonMode) {
258
286
  console.log(JSON.stringify({ error: 'No valid URLs found in input', code: 'ERR_NO_INPUT' }, null, 2));
259
287
  } else {
260
- console.error(chalk.red('No valid URLs found in input.'));
288
+ console.error(chalk.red('Error: No valid URLs found in input.'));
261
289
  }
262
290
  process.exit(EXIT.BAD_ARGS);
263
291
  return;
@@ -391,7 +419,7 @@ program
391
419
  console.log(chalk.gray(' โ€ข Batch scanning'));
392
420
  console.log('');
393
421
  } catch (err) {
394
- console.error(chalk.red.bold('\nโŒ Activation failed:'), err.message);
422
+ console.error(chalk.red('Error:'), err.message);
395
423
  console.error('');
396
424
  process.exit(EXIT.CONFIG_ERROR);
397
425
  }
@@ -474,7 +502,7 @@ program
474
502
  if (jsonMode) {
475
503
  console.log(JSON.stringify({ error: err.message, code: 'ERR_UPDATE' }, null, 2));
476
504
  } else {
477
- console.error(chalk.red.bold('\nโŒ Update check failed:'), err.message);
505
+ console.error(chalk.red('Error:'), err.message);
478
506
  console.error('');
479
507
  }
480
508
  process.exit(EXIT.NETWORK_ERROR);
@@ -503,6 +531,8 @@ ${chalk.bold('Examples:')}
503
531
  ${chalk.cyan('mpx-scan example.com --json')} JSON output
504
532
  ${chalk.cyan('mpx-scan example.com --fix nginx')} Generate nginx config
505
533
  ${chalk.cyan('mpx-scan example.com --brief')} One-line summary
534
+ ${chalk.cyan('mpx-scan example.com --pdf')} Export PDF report
535
+ ${chalk.cyan('mpx-scan example.com --pdf report.pdf')} Export PDF to specific file
506
536
  ${chalk.cyan('mpx-scan --schema')} Show tool schema (JSON)
507
537
  ${chalk.cyan('cat urls.txt | mpx-scan --batch --json')} Batch scan from stdin
508
538
  ${chalk.cyan('mpx-scan mcp')} Start MCP server
@@ -510,10 +540,8 @@ ${chalk.bold('Examples:')}
510
540
 
511
541
  ${chalk.bold('Exit Codes:')}
512
542
  0 Success, no issues found
513
- 1 Success, issues found
514
- 2 Invalid arguments
515
- 3 Configuration error (license, rate limit)
516
- 4 Network/connectivity error
543
+ 1 Error or issues found
544
+ 2 Invalid usage or bad arguments
517
545
 
518
546
  ${chalk.bold('Free vs Pro:')}
519
547
  ${chalk.yellow('Free:')} 3 scans/day, basic checks (headers, SSL, server)
@@ -522,4 +550,15 @@ ${chalk.bold('Free vs Pro:')}
522
550
  ${chalk.blue('Upgrade: https://mesaplex.com/mpx-scan')}
523
551
  `);
524
552
 
525
- program.parse();
553
+ try {
554
+ program.parse();
555
+ } catch (err) {
556
+ if (err.code === 'commander.version') {
557
+ process.exit(0);
558
+ }
559
+ if (err.code !== 'commander.help' && err.code !== 'commander.helpDisplayed') {
560
+ const msg = err.message.startsWith('error:') ? `Error: ${err.message.slice(7)}` : `Error: ${err.message}`;
561
+ console.error(chalk.red(msg));
562
+ process.exit(EXIT.BAD_ARGS);
563
+ }
564
+ }
package/package.json CHANGED
@@ -1,16 +1,24 @@
1
1
  {
2
2
  "name": "mpx-scan",
3
- "version": "1.3.0",
4
- "description": "Professional website security scanner CLI. Check headers, SSL, cookies, DNS, and get actionable fix suggestions. Part of the Mesaplex developer toolchain.",
3
+ "version": "1.3.1",
4
+ "description": "Website security scanner CLI. Headers, SSL, cookies, and DNS auditing. AI-native with JSON output and MCP server.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "mpx-scan": "bin/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "node test/run.js",
11
- "start": "node bin/cli.js"
10
+ "test": "node test/run.js"
12
11
  },
12
+ "funding": "https://mesaplex.com/pricing",
13
13
  "keywords": [
14
+ "cli",
15
+ "devtools",
16
+ "mesaplex",
17
+ "ai-native",
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "automation",
21
+ "json-output",
14
22
  "security",
15
23
  "scanner",
16
24
  "headers",
@@ -20,16 +28,10 @@
20
28
  "owasp",
21
29
  "devops",
22
30
  "ci-cd",
23
- "mesaplex",
24
- "devtools",
25
31
  "security-headers",
26
32
  "ssl-check",
27
33
  "dns-security",
28
- "cors",
29
- "mcp",
30
- "ai-native",
31
- "model-context-protocol",
32
- "automation"
34
+ "cors"
33
35
  ],
34
36
  "author": "Mesaplex <support@mesaplex.com>",
35
37
  "license": "SEE LICENSE IN LICENSE",
@@ -38,20 +40,22 @@
38
40
  "url": "git+https://github.com/mesaplexdev/mpx-scan.git"
39
41
  },
40
42
  "homepage": "https://github.com/mesaplexdev/mpx-scan#readme",
41
- "bugs": "https://github.com/mesaplexdev/mpx-scan/issues",
43
+ "bugs": {
44
+ "url": "https://github.com/mesaplexdev/mpx-scan/issues"
45
+ },
42
46
  "engines": {
43
47
  "node": ">=18.0.0"
44
48
  },
45
49
  "dependencies": {
46
50
  "@modelcontextprotocol/sdk": "^1.26.0",
47
51
  "chalk": "^4.1.2",
48
- "commander": "^12.0.0"
52
+ "commander": "^12.0.0",
53
+ "pdfkit": "^0.17.2"
49
54
  },
50
55
  "files": [
51
56
  "src/",
52
57
  "bin/",
53
58
  "README.md",
54
- "LICENSE",
55
- "package.json"
59
+ "LICENSE"
56
60
  ]
57
61
  }
package/src/index.js CHANGED
@@ -52,7 +52,7 @@ function checkConnectivity(parsedUrl, timeoutMs) {
52
52
  path: parsedUrl.pathname || '/',
53
53
  method: 'HEAD',
54
54
  timeout: timeoutMs,
55
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner' },
55
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner' },
56
56
  rejectUnauthorized: false,
57
57
  }, (res) => {
58
58
  clearTimeout(timer);
@@ -62,7 +62,7 @@ function checkConnectivity(parsedUrl, timeoutMs) {
62
62
 
63
63
  req.on('error', (err) => {
64
64
  clearTimeout(timer);
65
- if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET') {
65
+ if (err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN' || err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET') {
66
66
  reject(err);
67
67
  } else {
68
68
  resolve(); // Other errors (like SSL) are fine โ€” host is reachable
@@ -0,0 +1,218 @@
1
+ /**
2
+ * PDF Report Generator for mpx-scan
3
+ *
4
+ * Generates professional security scan reports using PDFKit
5
+ */
6
+
7
+ const PDFDocument = require('pdfkit');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const pkg = require('../../package.json');
11
+
12
+ // Color palette
13
+ const COLORS = {
14
+ primary: '#1a56db',
15
+ dark: '#1f2937',
16
+ gray: '#6b7280',
17
+ lightGray: '#e5e7eb',
18
+ white: '#ffffff',
19
+ pass: '#16a34a',
20
+ warn: '#ea580c',
21
+ fail: '#dc2626',
22
+ info: '#2563eb',
23
+ error: '#dc2626',
24
+ headerBg: '#1e3a5f',
25
+ sectionBg: '#f3f4f6',
26
+ };
27
+
28
+ const STATUS_LABELS = {
29
+ pass: 'โœ“ PASS',
30
+ warn: 'โš  WARNING',
31
+ fail: 'โœ— FAIL',
32
+ info: 'โ„น INFO',
33
+ error: 'โœ— ERROR',
34
+ };
35
+
36
+ const SECTION_NAMES = {
37
+ headers: 'Security Headers',
38
+ ssl: 'SSL/TLS',
39
+ cookies: 'Cookies',
40
+ server: 'Server Configuration',
41
+ exposedFiles: 'Exposed Files',
42
+ dns: 'DNS Security',
43
+ sri: 'Subresource Integrity',
44
+ mixedContent: 'Mixed Content',
45
+ redirects: 'Redirects',
46
+ };
47
+
48
+ /**
49
+ * Generate a PDF report from scan results
50
+ * @param {object} results - Scan results object
51
+ * @param {string} outputPath - Path to write the PDF
52
+ * @returns {Promise<string>} - Resolved path of the generated PDF
53
+ */
54
+ function generatePDF(results, outputPath) {
55
+ return new Promise((resolve, reject) => {
56
+ try {
57
+ const doc = new PDFDocument({
58
+ size: 'A4',
59
+ margins: { top: 50, bottom: 50, left: 50, right: 50 },
60
+ info: {
61
+ Title: `mpx-scan Security Report โ€” ${results.hostname}`,
62
+ Author: 'mpx-scan',
63
+ Subject: 'Website Security Scan Report',
64
+ Creator: `mpx-scan v${pkg.version}`,
65
+ },
66
+ bufferPages: true,
67
+ });
68
+
69
+ const stream = fs.createWriteStream(outputPath);
70
+ doc.pipe(stream);
71
+
72
+ const pageWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
73
+ const now = new Date().toLocaleDateString('en-US', {
74
+ year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit',
75
+ });
76
+
77
+ // โ”€โ”€โ”€ Header โ”€โ”€โ”€
78
+ doc.rect(0, 0, doc.page.width, 100).fill(COLORS.headerBg);
79
+ doc.fontSize(22).fillColor(COLORS.white).font('Helvetica-Bold')
80
+ .text('mpx-scan Security Report', 50, 30);
81
+ doc.fontSize(10).fillColor('#a0b4cc').font('Helvetica')
82
+ .text(`v${pkg.version} โ€ข ${now} โ€ข ${results.url}`, 50, 60);
83
+
84
+ doc.y = 120;
85
+
86
+ // โ”€โ”€โ”€ Summary Box โ”€โ”€โ”€
87
+ const scorePercent = results.maxScore > 0 ? Math.round((results.score / results.maxScore) * 100) : 0;
88
+ const gradeColor = scorePercent >= 85 ? COLORS.pass : scorePercent >= 55 ? COLORS.warn : COLORS.fail;
89
+
90
+ doc.roundedRect(50, doc.y, pageWidth, 90, 6).fill(COLORS.sectionBg);
91
+ const summaryTop = doc.y + 15;
92
+
93
+ // Grade circle
94
+ doc.circle(100, summaryTop + 30, 28).fill(gradeColor);
95
+ doc.fontSize(26).fillColor(COLORS.white).font('Helvetica-Bold')
96
+ .text(results.grade, 100 - 18, summaryTop + 16, { width: 36, align: 'center' });
97
+
98
+ // Score text
99
+ doc.fontSize(16).fillColor(COLORS.dark).font('Helvetica-Bold')
100
+ .text(`${scorePercent}/100`, 150, summaryTop + 5);
101
+ doc.fontSize(10).fillColor(COLORS.gray).font('Helvetica')
102
+ .text(`${results.score}/${results.maxScore} points โ€ข ${results.scanDuration}ms scan`, 150, summaryTop + 28);
103
+
104
+ // Counts
105
+ const countsX = 360;
106
+ const counts = [
107
+ { label: 'Passed', count: results.summary.passed, color: COLORS.pass },
108
+ { label: 'Warnings', count: results.summary.warnings, color: COLORS.warn },
109
+ { label: 'Failed', count: results.summary.failed, color: COLORS.fail },
110
+ { label: 'Info', count: results.summary.info, color: COLORS.info },
111
+ ];
112
+ counts.forEach((c, i) => {
113
+ const cx = countsX + i * 55;
114
+ doc.fontSize(18).fillColor(c.color).font('Helvetica-Bold')
115
+ .text(String(c.count), cx, summaryTop + 5, { width: 50, align: 'center' });
116
+ doc.fontSize(7).fillColor(COLORS.gray).font('Helvetica')
117
+ .text(c.label, cx, summaryTop + 28, { width: 50, align: 'center' });
118
+ });
119
+
120
+ doc.y = summaryTop + 75;
121
+
122
+ // โ”€โ”€โ”€ Detailed Findings โ”€โ”€โ”€
123
+ for (const [sectionKey, section] of Object.entries(results.sections)) {
124
+ const sectionName = SECTION_NAMES[sectionKey] || sectionKey;
125
+ const sectionPercent = section.maxScore > 0 ? Math.round((section.score / section.maxScore) * 100) : 0;
126
+
127
+ // Check if we need a new page (need at least 100px for header + 1 check)
128
+ if (doc.y > doc.page.height - 150) {
129
+ doc.addPage();
130
+ doc.y = 50;
131
+ }
132
+
133
+ // Section header
134
+ doc.y += 10;
135
+ doc.roundedRect(50, doc.y, pageWidth, 28, 4).fill(COLORS.primary);
136
+ doc.fontSize(11).fillColor(COLORS.white).font('Helvetica-Bold')
137
+ .text(`${sectionName}`, 60, doc.y + 7);
138
+ doc.fontSize(9).fillColor('#c0d4f0').font('Helvetica')
139
+ .text(`${section.grade} โ€ข ${sectionPercent}% (${section.score}/${section.maxScore})`, 60, doc.y + 7, {
140
+ width: pageWidth - 20, align: 'right'
141
+ });
142
+ doc.y += 35;
143
+
144
+ // Checks
145
+ for (const check of section.checks) {
146
+ if (doc.y > doc.page.height - 100) {
147
+ doc.addPage();
148
+ doc.y = 50;
149
+ }
150
+
151
+ const statusColor = COLORS[check.status] || COLORS.gray;
152
+ const statusLabel = STATUS_LABELS[check.status] || check.status.toUpperCase();
153
+
154
+ // Status badge
155
+ doc.fontSize(7).fillColor(statusColor).font('Helvetica-Bold')
156
+ .text(statusLabel, 60, doc.y);
157
+
158
+ // Check name
159
+ doc.fontSize(10).fillColor(COLORS.dark).font('Helvetica-Bold')
160
+ .text(check.name, 130, doc.y);
161
+ doc.y += 15;
162
+
163
+ // Message
164
+ if (check.message) {
165
+ doc.fontSize(9).fillColor(COLORS.gray).font('Helvetica')
166
+ .text(check.message, 130, doc.y, { width: pageWidth - 90 });
167
+ doc.y += doc.heightOfString(check.message, { width: pageWidth - 90, fontSize: 9 }) + 3;
168
+ }
169
+
170
+ // Recommendation
171
+ if (check.recommendation) {
172
+ doc.fontSize(8).fillColor(COLORS.primary).font('Helvetica-Oblique')
173
+ .text(`โ†’ ${check.recommendation}`, 130, doc.y, { width: pageWidth - 90 });
174
+ doc.y += doc.heightOfString(`โ†’ ${check.recommendation}`, { width: pageWidth - 90, fontSize: 8 }) + 3;
175
+ }
176
+
177
+ doc.y += 8;
178
+ }
179
+ }
180
+
181
+ // โ”€โ”€โ”€ Footer on every page โ”€โ”€โ”€
182
+ const range = doc.bufferedPageRange();
183
+ for (let i = range.start; i < range.start + range.count; i++) {
184
+ doc.switchToPage(i);
185
+ const footerY = doc.page.height - 35;
186
+ doc.fontSize(7).fillColor(COLORS.gray).font('Helvetica')
187
+ .text(
188
+ `Generated by mpx-scan v${pkg.version} on ${now}`,
189
+ 50, footerY, { width: pageWidth, align: 'center' }
190
+ );
191
+ doc.text(
192
+ `Page ${i + 1} of ${range.count}`,
193
+ 50, footerY + 12, { width: pageWidth, align: 'center' }
194
+ );
195
+ }
196
+
197
+ doc.end();
198
+
199
+ stream.on('finish', () => resolve(outputPath));
200
+ stream.on('error', reject);
201
+ } catch (err) {
202
+ reject(err);
203
+ }
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Get default PDF filename for a scan
209
+ * @param {string} hostname - Target hostname
210
+ * @returns {string} Default filename
211
+ */
212
+ function getDefaultPDFFilename(hostname) {
213
+ const date = new Date().toISOString().slice(0, 10);
214
+ const safeName = hostname.replace(/[^a-zA-Z0-9.-]/g, '_');
215
+ return `mpx-scan-report-${safeName}-${date}.pdf`;
216
+ }
217
+
218
+ module.exports = { generatePDF, getDefaultPDFFilename };
@@ -78,7 +78,7 @@ function fetchCookies(parsedUrl, options = {}) {
78
78
  const req = protocol.request(parsedUrl.href, {
79
79
  method: 'GET',
80
80
  timeout,
81
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
81
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
82
82
  rejectUnauthorized: false,
83
83
  }, (res) => {
84
84
  // Consume body
@@ -117,8 +117,14 @@ async function scanDNS(parsedUrl, options = {}) {
117
117
  try {
118
118
  const mxRecords = await withTimeout(resolver.resolveMx(rootDomain), dnsTimeout, `MX ${rootDomain}`);
119
119
  if (mxRecords && mxRecords.length > 0) {
120
- const mxList = mxRecords.sort((a, b) => a.priority - b.priority).map(r => `${r.exchange} (${r.priority})`);
121
- checks.push({ name: 'MX Records', status: 'info', message: `Mail servers: ${mxList.join(', ')}`, value: mxList.join(', ') });
120
+ const mxList = mxRecords.sort((a, b) => a.priority - b.priority).map(r => `${r.exchange || '.'} (${r.priority})`);
121
+ // Null MX (RFC 7505): single record with exchange "." and priority 0 means domain does not accept mail
122
+ const isNullMx = mxRecords.length === 1 && (!mxRecords[0].exchange || mxRecords[0].exchange === '.') && mxRecords[0].priority === 0;
123
+ if (isNullMx) {
124
+ checks.push({ name: 'MX Records', status: 'info', message: 'Null MX โ€” domain does not accept email', value: '. (0)' });
125
+ } else {
126
+ checks.push({ name: 'MX Records', status: 'info', message: `Mail servers: ${mxList.join(', ')}`, value: mxList.join(', ') });
127
+ }
122
128
  }
123
129
  } catch { /* MX is informational only */ }
124
130
 
@@ -151,7 +151,7 @@ function checkPath(parsedUrl, pathStr, options = {}) {
151
151
  const req = protocol.request(url.href, {
152
152
  method: 'GET',
153
153
  timeout,
154
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
154
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
155
155
  rejectUnauthorized: false,
156
156
  }, (res) => {
157
157
  let body = '';
@@ -305,7 +305,7 @@ function fetchHeaders(parsedUrl, options = {}) {
305
305
 
306
306
  const req = protocol.request(parsedUrl.href, {
307
307
  method, timeout,
308
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
308
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
309
309
  rejectUnauthorized: false,
310
310
  }, (res) => {
311
311
  if (method === 'GET') { res.resume(); }
@@ -191,7 +191,7 @@ function fetchHeaders(parsedUrl, options = {}) {
191
191
  method,
192
192
  timeout,
193
193
  headers: {
194
- 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)'
194
+ 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)'
195
195
  },
196
196
  rejectUnauthorized: false // We check SSL separately
197
197
  }, (res) => {
@@ -86,7 +86,7 @@ function followRedirect(testUrl, options = {}) {
86
86
  const req = protocol.request(testUrl.href, {
87
87
  method: 'GET',
88
88
  timeout,
89
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
89
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
90
90
  rejectUnauthorized: false,
91
91
  }, (res) => {
92
92
  res.resume(); // Consume body
@@ -88,7 +88,7 @@ function checkRedirectToHttps(httpUrl, options = {}) {
88
88
  const req = http.request(httpUrl.href, {
89
89
  method,
90
90
  timeout,
91
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
91
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
92
92
  }, (res) => {
93
93
  if (method === 'GET') { res.resume(); }
94
94
  if (res.statusCode >= 300 && res.statusCode < 400) {
@@ -115,7 +115,7 @@ function fetchWithOrigin(parsedUrl, options = {}) {
115
115
  method,
116
116
  timeout,
117
117
  headers: {
118
- 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)',
118
+ 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)',
119
119
  'Origin': 'https://evil.example.com'
120
120
  },
121
121
  rejectUnauthorized: false,
@@ -141,7 +141,7 @@ function checkMethods(parsedUrl, options = {}) {
141
141
  const req = protocol.request(parsedUrl.href, {
142
142
  method: 'OPTIONS',
143
143
  timeout,
144
- headers: { 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
144
+ headers: { 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)' },
145
145
  rejectUnauthorized: false,
146
146
  }, (res) => {
147
147
  const allow = res.headers.allow || '';
@@ -125,7 +125,7 @@ function fetchBody(parsedUrl, options = {}) {
125
125
  method: 'GET',
126
126
  timeout,
127
127
  headers: {
128
- 'User-Agent': 'mpx-scan/1.2.1 Security Scanner (https://github.com/mesaplexdev/mpx-scan)',
128
+ 'User-Agent': 'mpx-scan/1.3.0 Security Scanner (https://github.com/mesaplexdev/mpx-scan)',
129
129
  'Accept': 'text/html'
130
130
  },
131
131
  rejectUnauthorized: false,
package/src/schema.js CHANGED
@@ -194,6 +194,40 @@ function getSchema() {
194
194
  free: SCANNER_TIERS.free,
195
195
  pro: SCANNER_TIERS.pro
196
196
  },
197
+ exitCodes: {
198
+ 0: 'Success / no issues',
199
+ 1: 'Error or security issues found'
200
+ },
201
+ globalFlags: {
202
+ '--json': {
203
+ type: 'boolean',
204
+ default: false,
205
+ description: 'Output results as structured JSON'
206
+ },
207
+ '--quiet': {
208
+ type: 'boolean',
209
+ default: false,
210
+ description: 'Suppress non-essential output (no banners or progress)'
211
+ },
212
+ '--no-color': {
213
+ type: 'boolean',
214
+ default: false,
215
+ description: 'Disable ANSI color codes in output'
216
+ },
217
+ '--schema': {
218
+ type: 'boolean',
219
+ default: false,
220
+ description: 'Output this schema as JSON'
221
+ },
222
+ '--version': {
223
+ type: 'boolean',
224
+ description: 'Show version number'
225
+ },
226
+ '--help': {
227
+ type: 'boolean',
228
+ description: 'Show help information'
229
+ }
230
+ },
197
231
  mcpConfig: {
198
232
  description: 'Add to your MCP client configuration to use mpx-scan as an AI tool',
199
233
  config: {