chrome-cdp-cli 1.9.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/README.md +105 -16
- package/dist/cli/ArgumentParser.js +506 -0
- package/dist/cli/CLIApplication.js +91 -6
- package/dist/cli/CLIInterface.js +25 -82
- package/dist/cli/CommandRouter.js +4 -4
- package/dist/cli/CommandSchemaRegistry.js +614 -0
- package/dist/cli/EnhancedCLIInterface.js +230 -0
- package/dist/cli/HelpSystem.js +683 -0
- package/dist/cli/OutputFormatter.js +367 -0
- package/dist/cli/OutputManager.js +151 -0
- package/dist/cli/demo-enhanced-help.js +88 -0
- package/dist/cli/demo-enhanced-parser.js +79 -0
- package/dist/cli/demo-output-formatting.js +261 -0
- package/dist/cli/index.js +24 -4
- package/dist/cli/interfaces/ArgumentParser.js +2 -0
- package/dist/config/ConfigurationManager.js +357 -0
- package/dist/config/example.js +54 -0
- package/dist/config/index.js +21 -0
- package/dist/config/interfaces.js +35 -0
- package/dist/config/utils.js +238 -0
- package/dist/handlers/InstallClaudeSkillHandler.js +271 -124
- package/dist/handlers/InstallCursorCommandHandler.js +32 -133
- package/dist/handlers/ListConsoleMessagesHandler.js +63 -8
- package/dist/handlers/ListNetworkRequestsHandler.js +37 -3
- package/dist/handlers/TakeSnapshotHandler.js +28 -8
- package/dist/handlers/index.js +0 -2
- package/dist/proxy/server/MessageStore.js +11 -80
- package/package.json +3 -1
- package/dist/handlers/GetConsoleMessageHandler.js +0 -161
- package/dist/handlers/GetNetworkRequestHandler.js +0 -108
package/README.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
A command-line tool for browser automation via Chrome DevTools Protocol (CDP). Designed for developers who need reliable, scriptable browser control with both dedicated commands and flexible JavaScript execution.
|
|
4
4
|
|
|
5
|
+
## 🚀 Enhanced CLI Features
|
|
6
|
+
|
|
7
|
+
**New in v2.0:** The CLI now includes a comprehensive refactored parameter system with:
|
|
8
|
+
|
|
9
|
+
- 🔧 **Advanced Configuration Management**: YAML/JSON config files with profiles and precedence handling
|
|
10
|
+
- ⚙️ **Enhanced Argument Parser**: Consistent option handling with validation and schema support
|
|
11
|
+
- 📚 **Comprehensive Help System**: Contextual help, advanced topics, and detailed examples
|
|
12
|
+
- 🎯 **Standardized Output**: JSON/text formats with quiet/verbose modes and custom templates
|
|
13
|
+
- 🔗 **Command Aliasing**: Built-in and custom aliases for efficient workflows
|
|
14
|
+
- 🧩 **Plugin Architecture**: Extensible system for custom commands and functionality
|
|
15
|
+
- 🖥️ **Interactive Mode**: Command prompt with tab completion and session management
|
|
16
|
+
- ⚡ **Performance Optimizations**: Configuration caching and connection reuse
|
|
17
|
+
|
|
5
18
|
## 🤔 Why This Tool Exists
|
|
6
19
|
|
|
7
20
|
**The honest story:** I started using `chrome-devtools-mcp` like everyone else. It worked great... until it didn't. One day it just stopped working - Cursor showed 26 tools available, everything looked normal, but every single tool call threw errors. Classic black box problem: you can't debug what you can't see inside.
|
|
@@ -882,27 +895,103 @@ For detailed documentation, see the [Form Filling Guide](docs/FORM_FILLING.md).
|
|
|
882
895
|
|
|
883
896
|
## Configuration
|
|
884
897
|
|
|
885
|
-
|
|
898
|
+
The CLI supports comprehensive configuration management with multiple sources and precedence handling.
|
|
899
|
+
|
|
900
|
+
### Quick Configuration
|
|
901
|
+
|
|
902
|
+
Create a `.chrome-cdp-cli.yaml` file in your project root or home directory:
|
|
903
|
+
|
|
904
|
+
```yaml
|
|
905
|
+
# Basic configuration
|
|
906
|
+
host: localhost
|
|
907
|
+
port: 9222
|
|
908
|
+
timeout: 30000
|
|
909
|
+
outputFormat: text
|
|
910
|
+
verbose: false
|
|
911
|
+
|
|
912
|
+
# Profiles for different environments
|
|
913
|
+
profiles:
|
|
914
|
+
development:
|
|
915
|
+
debug: true
|
|
916
|
+
verbose: true
|
|
917
|
+
production:
|
|
918
|
+
quiet: true
|
|
919
|
+
outputFormat: json
|
|
920
|
+
|
|
921
|
+
# Command aliases
|
|
922
|
+
aliases:
|
|
923
|
+
ss: screenshot
|
|
924
|
+
js: eval
|
|
925
|
+
health: eval "document.readyState === 'complete'"
|
|
926
|
+
|
|
927
|
+
# Command-specific defaults
|
|
928
|
+
commands:
|
|
929
|
+
screenshot:
|
|
930
|
+
defaults:
|
|
931
|
+
format: png
|
|
932
|
+
quality: 90
|
|
933
|
+
```
|
|
886
934
|
|
|
887
|
-
|
|
935
|
+
### Environment Variables
|
|
888
936
|
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
"verbose": false,
|
|
896
|
-
"quiet": false
|
|
897
|
-
}
|
|
937
|
+
```bash
|
|
938
|
+
export CHROME_CDP_CLI_HOST=localhost
|
|
939
|
+
export CHROME_CDP_CLI_PORT=9222
|
|
940
|
+
export CHROME_CDP_CLI_TIMEOUT=30000
|
|
941
|
+
export CHROME_CDP_CLI_VERBOSE=true
|
|
942
|
+
export CHROME_CDP_CLI_PROFILE=development
|
|
898
943
|
```
|
|
899
944
|
|
|
900
|
-
###
|
|
945
|
+
### Configuration Precedence
|
|
946
|
+
|
|
947
|
+
Configuration values are resolved in order (highest to lowest priority):
|
|
948
|
+
1. **Command-line arguments** (highest)
|
|
949
|
+
2. **Environment variables**
|
|
950
|
+
3. **Configuration files** (profile-specific, then default)
|
|
951
|
+
4. **Default values** (lowest)
|
|
952
|
+
|
|
953
|
+
### Advanced Configuration
|
|
954
|
+
|
|
955
|
+
For comprehensive configuration options, see the [Configuration Guide](docs/CONFIGURATION.md).
|
|
956
|
+
|
|
957
|
+
## Enhanced Help System
|
|
958
|
+
|
|
959
|
+
The CLI includes a comprehensive help system with contextual assistance:
|
|
960
|
+
|
|
961
|
+
```bash
|
|
962
|
+
# General help with categorized commands
|
|
963
|
+
chrome-cdp-cli help
|
|
964
|
+
|
|
965
|
+
# Command-specific help with examples
|
|
966
|
+
chrome-cdp-cli help eval
|
|
967
|
+
chrome-cdp-cli help screenshot
|
|
968
|
+
|
|
969
|
+
# Advanced help topics
|
|
970
|
+
chrome-cdp-cli help topic configuration
|
|
971
|
+
chrome-cdp-cli help topic selectors
|
|
972
|
+
chrome-cdp-cli help topic automation
|
|
973
|
+
chrome-cdp-cli help topic debugging
|
|
974
|
+
|
|
975
|
+
# Contextual help (shown automatically on errors)
|
|
976
|
+
chrome-cdp-cli click "#nonexistent" # Shows selector help
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
## Plugin System
|
|
980
|
+
|
|
981
|
+
Extend the CLI with custom commands and functionality:
|
|
982
|
+
|
|
983
|
+
```bash
|
|
984
|
+
# Install plugins
|
|
985
|
+
npm install -g chrome-cdp-cli-plugin-form-automation
|
|
986
|
+
|
|
987
|
+
# Load plugins from directory
|
|
988
|
+
chrome-cdp-cli --plugin-dir ./plugins custom-command
|
|
989
|
+
|
|
990
|
+
# List available plugins
|
|
991
|
+
chrome-cdp-cli --show-plugins
|
|
992
|
+
```
|
|
901
993
|
|
|
902
|
-
|
|
903
|
-
- `CHROME_CLI_PORT`: Default DevTools port
|
|
904
|
-
- `CHROME_CLI_TIMEOUT`: Default command timeout
|
|
905
|
-
- `CHROME_CLI_FORMAT`: Default output format
|
|
994
|
+
For plugin development, see the [Plugin Development Guide](docs/PLUGIN_DEVELOPMENT.md).
|
|
906
995
|
|
|
907
996
|
## Development
|
|
908
997
|
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ArgumentParser = void 0;
|
|
4
|
+
const HelpSystem_1 = require("./HelpSystem");
|
|
5
|
+
class ArgumentParser {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.commands = new Map();
|
|
8
|
+
this.aliases = new Map();
|
|
9
|
+
this.helpSystem = new HelpSystem_1.HelpSystem(undefined, this);
|
|
10
|
+
}
|
|
11
|
+
parseArguments(argv) {
|
|
12
|
+
try {
|
|
13
|
+
if (!Array.isArray(argv)) {
|
|
14
|
+
return this.createParseResult('help', {}, [], true);
|
|
15
|
+
}
|
|
16
|
+
const sliceStart = Math.max(0, Math.min(2, argv.length));
|
|
17
|
+
const args = argv.slice(sliceStart);
|
|
18
|
+
if (args.length === 0) {
|
|
19
|
+
return this.createParseResult('help', {}, [], true);
|
|
20
|
+
}
|
|
21
|
+
if (args.includes('--help')) {
|
|
22
|
+
return this.createParseResult('help', {}, [], true);
|
|
23
|
+
}
|
|
24
|
+
if (args.includes('--version') || args.includes('-V')) {
|
|
25
|
+
return this.createParseResult('version', {}, [], true);
|
|
26
|
+
}
|
|
27
|
+
let globalOptions;
|
|
28
|
+
let commandName;
|
|
29
|
+
let commandArgs;
|
|
30
|
+
try {
|
|
31
|
+
const parseResult = this.parseGlobalAndCommand(args);
|
|
32
|
+
globalOptions = parseResult.globalOptions;
|
|
33
|
+
commandName = parseResult.commandName;
|
|
34
|
+
commandArgs = parseResult.commandArgs;
|
|
35
|
+
}
|
|
36
|
+
catch (parseError) {
|
|
37
|
+
if (args.some(arg => arg === '--help' || arg === 'help')) {
|
|
38
|
+
return this.createParseResult('help', {}, [], true);
|
|
39
|
+
}
|
|
40
|
+
throw parseError;
|
|
41
|
+
}
|
|
42
|
+
const resolvedCommand = this.resolveCommandName(commandName);
|
|
43
|
+
if (resolvedCommand === 'help') {
|
|
44
|
+
return this.createParseResult(resolvedCommand, globalOptions, [], true, commandArgs);
|
|
45
|
+
}
|
|
46
|
+
const commandDef = this.commands.get(resolvedCommand);
|
|
47
|
+
if (!commandDef) {
|
|
48
|
+
return this.createParseResult(resolvedCommand, globalOptions, [`Unknown command: ${commandName}. Use 'help' to see available commands.`], false);
|
|
49
|
+
}
|
|
50
|
+
const parseResult = this.parseCommandArguments(commandDef, commandArgs);
|
|
51
|
+
const allOptions = { ...globalOptions, ...parseResult.options };
|
|
52
|
+
return this.createParseResult(resolvedCommand, allOptions, parseResult.errors, parseResult.errors.length === 0, parseResult.arguments);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
56
|
+
if (process.env.DEBUG) {
|
|
57
|
+
console.error('Parse error details:', error);
|
|
58
|
+
if (error instanceof Error && error.stack) {
|
|
59
|
+
console.error('Stack trace:', error.stack);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (errorMessage.includes('Invalid count value')) {
|
|
63
|
+
return this.createParseResult('help', {}, [`Parse error: Invalid argument format. Please check your command syntax.`], false);
|
|
64
|
+
}
|
|
65
|
+
return this.createParseResult('help', {}, [`Parse error: ${errorMessage}`], false);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
registerCommand(command) {
|
|
69
|
+
const validation = this.validateCommandDefinition(command);
|
|
70
|
+
if (!validation.valid) {
|
|
71
|
+
throw new Error(`Invalid command definition: ${validation.errors.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
this.commands.set(command.name, command);
|
|
74
|
+
for (const alias of command.aliases) {
|
|
75
|
+
if (this.aliases.has(alias)) {
|
|
76
|
+
throw new Error(`Alias '${alias}' is already registered for command '${this.aliases.get(alias)}'`);
|
|
77
|
+
}
|
|
78
|
+
this.aliases.set(alias, command.name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
generateHelp(command) {
|
|
82
|
+
if (command) {
|
|
83
|
+
if (command.startsWith('topic ')) {
|
|
84
|
+
const topicName = command.substring(6);
|
|
85
|
+
return this.helpSystem.generateTopicHelp(topicName);
|
|
86
|
+
}
|
|
87
|
+
return this.helpSystem.generateCommandHelp(command);
|
|
88
|
+
}
|
|
89
|
+
return this.helpSystem.generateGeneralHelp();
|
|
90
|
+
}
|
|
91
|
+
validateArguments(command, args) {
|
|
92
|
+
const commandDef = this.commands.get(command);
|
|
93
|
+
if (!commandDef) {
|
|
94
|
+
return {
|
|
95
|
+
valid: false,
|
|
96
|
+
errors: [`Unknown command: ${command}`],
|
|
97
|
+
warnings: []
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const errors = [];
|
|
101
|
+
const warnings = [];
|
|
102
|
+
for (const option of commandDef.options) {
|
|
103
|
+
if (option.required && !(option.name in args.options)) {
|
|
104
|
+
errors.push(`Required option --${option.name} is missing`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const requiredArgs = commandDef.arguments.filter(arg => arg.required);
|
|
108
|
+
if (args.arguments.length < requiredArgs.length) {
|
|
109
|
+
const providedCount = Math.max(0, Math.min(args.arguments.length, requiredArgs.length));
|
|
110
|
+
const missingCount = Math.max(0, requiredArgs.length - providedCount);
|
|
111
|
+
if (missingCount > 0) {
|
|
112
|
+
const startIndex = Math.max(0, providedCount);
|
|
113
|
+
const endIndex = Math.min(requiredArgs.length, startIndex + missingCount);
|
|
114
|
+
if (startIndex < endIndex && startIndex >= 0 && endIndex <= requiredArgs.length) {
|
|
115
|
+
const missingArgs = requiredArgs.slice(startIndex, endIndex).map(arg => arg.name);
|
|
116
|
+
if (missingArgs.length > 0) {
|
|
117
|
+
errors.push(`Missing required arguments: ${missingArgs.join(', ')}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const [optionName, value] of Object.entries(args.options)) {
|
|
123
|
+
const optionDef = commandDef.options.find(opt => opt.name === optionName);
|
|
124
|
+
if (optionDef) {
|
|
125
|
+
const optionValidation = this.validateOptionValue(optionDef, value);
|
|
126
|
+
if (!optionValidation.valid) {
|
|
127
|
+
errors.push(...optionValidation.errors);
|
|
128
|
+
}
|
|
129
|
+
warnings.push(...optionValidation.warnings);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (let i = 0; i < args.arguments.length; i++) {
|
|
133
|
+
const argDef = commandDef.arguments[i];
|
|
134
|
+
if (argDef) {
|
|
135
|
+
const argValidation = this.validateArgumentValue(argDef, args.arguments[i]);
|
|
136
|
+
if (!argValidation.valid) {
|
|
137
|
+
errors.push(...argValidation.errors);
|
|
138
|
+
}
|
|
139
|
+
warnings.push(...argValidation.warnings);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
valid: errors.length === 0,
|
|
144
|
+
errors,
|
|
145
|
+
warnings
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
parseGlobalAndCommand(args) {
|
|
149
|
+
if (args.includes('--help')) {
|
|
150
|
+
return { globalOptions: {}, commandName: 'help', commandArgs: [] };
|
|
151
|
+
}
|
|
152
|
+
const globalOptions = {};
|
|
153
|
+
let commandName = 'help';
|
|
154
|
+
let commandArgs = [];
|
|
155
|
+
let i = 0;
|
|
156
|
+
const globalOptionDefs = [
|
|
157
|
+
{ name: 'host', short: 'h', type: 'string', description: 'Chrome host address', default: 'localhost' },
|
|
158
|
+
{ name: 'port', short: 'p', type: 'number', description: 'DevTools port', default: 9222 },
|
|
159
|
+
{ name: 'format', short: 'f', type: 'string', description: 'Output format', choices: ['json', 'text'], default: 'text' },
|
|
160
|
+
{ name: 'verbose', short: 'v', type: 'boolean', description: 'Enable verbose logging', default: false },
|
|
161
|
+
{ name: 'quiet', short: 'q', type: 'boolean', description: 'Enable quiet mode', default: false },
|
|
162
|
+
{ name: 'timeout', short: 't', type: 'number', description: 'Command timeout in milliseconds', default: 30000 },
|
|
163
|
+
{ name: 'debug', short: 'd', type: 'boolean', description: 'Enable debug logging', default: false },
|
|
164
|
+
{ name: 'config', short: 'c', type: 'string', description: 'Configuration file path' }
|
|
165
|
+
];
|
|
166
|
+
while (i < args.length) {
|
|
167
|
+
const arg = args[i];
|
|
168
|
+
if (arg.startsWith('--')) {
|
|
169
|
+
const { option, value, consumed } = this.parseLongOption(arg, args, i, globalOptionDefs);
|
|
170
|
+
if (option) {
|
|
171
|
+
globalOptions[option.name] = value;
|
|
172
|
+
const validConsumed = Math.max(1, Math.min(consumed, 2));
|
|
173
|
+
i += validConsumed;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
commandName = arg.substring(2);
|
|
177
|
+
const nextIndex = i + 1;
|
|
178
|
+
if (nextIndex >= 0 && nextIndex <= args.length) {
|
|
179
|
+
commandArgs = args.slice(nextIndex);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
commandArgs = [];
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (arg.startsWith('-') && arg.length > 1) {
|
|
188
|
+
const { option, value, consumed } = this.parseShortOption(arg, args, i, globalOptionDefs);
|
|
189
|
+
if (option) {
|
|
190
|
+
globalOptions[option.name] = value;
|
|
191
|
+
const validConsumed = Math.max(1, Math.min(consumed || 1, 2));
|
|
192
|
+
i += validConsumed;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
commandName = arg.substring(1);
|
|
196
|
+
const nextIndex = i + 1;
|
|
197
|
+
if (nextIndex >= 0 && nextIndex <= args.length) {
|
|
198
|
+
commandArgs = args.slice(nextIndex);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
commandArgs = [];
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
commandName = arg;
|
|
208
|
+
const nextIndex = i + 1;
|
|
209
|
+
if (nextIndex >= 0 && nextIndex <= args.length) {
|
|
210
|
+
commandArgs = args.slice(nextIndex);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
commandArgs = [];
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { globalOptions, commandName, commandArgs };
|
|
219
|
+
}
|
|
220
|
+
parseLongOption(arg, args, index, optionDefs) {
|
|
221
|
+
let optionName = arg.substring(2);
|
|
222
|
+
let value;
|
|
223
|
+
let consumed = 1;
|
|
224
|
+
const equalIndex = optionName.indexOf('=');
|
|
225
|
+
if (equalIndex !== -1) {
|
|
226
|
+
value = optionName.substring(equalIndex + 1);
|
|
227
|
+
optionName = optionName.substring(0, equalIndex);
|
|
228
|
+
}
|
|
229
|
+
let isNegated = false;
|
|
230
|
+
if (optionName.startsWith('no-')) {
|
|
231
|
+
isNegated = true;
|
|
232
|
+
optionName = optionName.substring(3);
|
|
233
|
+
}
|
|
234
|
+
const optionDef = optionDefs.find(opt => opt.name === optionName);
|
|
235
|
+
if (!optionDef) {
|
|
236
|
+
return { option: null, value: null, consumed: 0 };
|
|
237
|
+
}
|
|
238
|
+
if (optionDef.type === 'boolean') {
|
|
239
|
+
value = !isNegated;
|
|
240
|
+
}
|
|
241
|
+
else if (value === undefined) {
|
|
242
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
243
|
+
value = args[index + 1];
|
|
244
|
+
consumed = 2;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
throw new Error(`Option --${optionName} requires a value`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
value = this.convertOptionValue(optionDef, value);
|
|
251
|
+
return { option: optionDef, value, consumed };
|
|
252
|
+
}
|
|
253
|
+
parseShortOption(arg, args, index, optionDefs) {
|
|
254
|
+
const shortName = arg.substring(1);
|
|
255
|
+
if (shortName.length > 1) {
|
|
256
|
+
return { option: null, value: null, consumed: 0 };
|
|
257
|
+
}
|
|
258
|
+
const optionDef = optionDefs.find(opt => opt.short === shortName);
|
|
259
|
+
if (!optionDef) {
|
|
260
|
+
return { option: null, value: null, consumed: 0 };
|
|
261
|
+
}
|
|
262
|
+
let value;
|
|
263
|
+
let consumed = 1;
|
|
264
|
+
if (optionDef.type === 'boolean') {
|
|
265
|
+
value = true;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
269
|
+
value = args[index + 1];
|
|
270
|
+
consumed = 2;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
throw new Error(`Option -${shortName} requires a value`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
value = this.convertOptionValue(optionDef, value);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
throw new Error(`Invalid value for option -${shortName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
281
|
+
}
|
|
282
|
+
if (consumed < 1 || consumed > 2) {
|
|
283
|
+
consumed = 1;
|
|
284
|
+
}
|
|
285
|
+
return { option: optionDef, value, consumed };
|
|
286
|
+
}
|
|
287
|
+
parseCommandArguments(commandDef, args) {
|
|
288
|
+
const options = {};
|
|
289
|
+
const arguments_ = [];
|
|
290
|
+
const errors = [];
|
|
291
|
+
let i = 0;
|
|
292
|
+
while (i < args.length) {
|
|
293
|
+
const arg = args[i];
|
|
294
|
+
if (arg.startsWith('--')) {
|
|
295
|
+
try {
|
|
296
|
+
const { option, value, consumed } = this.parseLongOption(arg, args, i, commandDef.options);
|
|
297
|
+
if (option) {
|
|
298
|
+
options[option.name] = value;
|
|
299
|
+
if (consumed > 0) {
|
|
300
|
+
i += consumed;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
errors.push(`Unknown option: ${arg}`);
|
|
308
|
+
i++;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
313
|
+
i++;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (arg.startsWith('-') && arg.length > 1) {
|
|
317
|
+
try {
|
|
318
|
+
const { option, value, consumed } = this.parseShortOption(arg, args, i, commandDef.options);
|
|
319
|
+
if (option) {
|
|
320
|
+
options[option.name] = value;
|
|
321
|
+
if (consumed > 0) {
|
|
322
|
+
i += consumed;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
i++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
errors.push(`Unknown option: ${arg}`);
|
|
330
|
+
i++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
335
|
+
i++;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
arguments_.push(arg);
|
|
340
|
+
i++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return { options, arguments: arguments_, errors };
|
|
344
|
+
}
|
|
345
|
+
convertOptionValue(optionDef, value) {
|
|
346
|
+
if (value === undefined || value === null) {
|
|
347
|
+
return optionDef.default;
|
|
348
|
+
}
|
|
349
|
+
const stringValue = String(value);
|
|
350
|
+
switch (optionDef.type) {
|
|
351
|
+
case 'number':
|
|
352
|
+
const numValue = Number(stringValue);
|
|
353
|
+
if (isNaN(numValue)) {
|
|
354
|
+
throw new Error(`Option --${optionDef.name} must be a number, got: ${stringValue}`);
|
|
355
|
+
}
|
|
356
|
+
return numValue;
|
|
357
|
+
case 'boolean':
|
|
358
|
+
if (typeof value === 'boolean') {
|
|
359
|
+
return value;
|
|
360
|
+
}
|
|
361
|
+
const lowerValue = stringValue.toLowerCase();
|
|
362
|
+
if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no') {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
throw new Error(`Option --${optionDef.name} must be a boolean, got: ${stringValue}`);
|
|
369
|
+
case 'array':
|
|
370
|
+
if (Array.isArray(value)) {
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
return stringValue.split(',').map(s => s.trim());
|
|
374
|
+
case 'string':
|
|
375
|
+
default:
|
|
376
|
+
return stringValue;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
validateOptionValue(optionDef, value) {
|
|
380
|
+
const errors = [];
|
|
381
|
+
const warnings = [];
|
|
382
|
+
if (optionDef.choices && optionDef.choices.length > 0) {
|
|
383
|
+
if (!optionDef.choices.includes(String(value))) {
|
|
384
|
+
errors.push(`Option --${optionDef.name} must be one of: ${optionDef.choices.join(', ')}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (optionDef.validator) {
|
|
388
|
+
const validationResult = optionDef.validator(value);
|
|
389
|
+
if (!validationResult.valid) {
|
|
390
|
+
errors.push(...validationResult.errors);
|
|
391
|
+
warnings.push(...validationResult.warnings);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
valid: errors.length === 0,
|
|
396
|
+
errors,
|
|
397
|
+
warnings
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
validateArgumentValue(argDef, value) {
|
|
401
|
+
const errors = [];
|
|
402
|
+
const warnings = [];
|
|
403
|
+
switch (argDef.type) {
|
|
404
|
+
case 'number':
|
|
405
|
+
if (isNaN(Number(value))) {
|
|
406
|
+
errors.push(`Argument ${argDef.name} must be a number, got: ${value}`);
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
case 'file':
|
|
410
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
411
|
+
errors.push(`Argument ${argDef.name} must be a valid file path`);
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
case 'url':
|
|
415
|
+
try {
|
|
416
|
+
new URL(String(value));
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
errors.push(`Argument ${argDef.name} must be a valid URL`);
|
|
420
|
+
}
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
if (argDef.validator) {
|
|
424
|
+
const validationResult = argDef.validator(value);
|
|
425
|
+
if (!validationResult.valid) {
|
|
426
|
+
errors.push(...validationResult.errors);
|
|
427
|
+
warnings.push(...validationResult.warnings);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
valid: errors.length === 0,
|
|
432
|
+
errors,
|
|
433
|
+
warnings
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
resolveCommandName(commandName) {
|
|
437
|
+
return this.aliases.get(commandName) || commandName;
|
|
438
|
+
}
|
|
439
|
+
validateCommandDefinition(command) {
|
|
440
|
+
const errors = [];
|
|
441
|
+
if (!command.name || typeof command.name !== 'string') {
|
|
442
|
+
errors.push('Command name is required and must be a string');
|
|
443
|
+
}
|
|
444
|
+
if (!command.description || typeof command.description !== 'string') {
|
|
445
|
+
errors.push('Command description is required and must be a string');
|
|
446
|
+
}
|
|
447
|
+
if (!Array.isArray(command.aliases)) {
|
|
448
|
+
errors.push('Command aliases must be an array');
|
|
449
|
+
}
|
|
450
|
+
if (!Array.isArray(command.options)) {
|
|
451
|
+
errors.push('Command options must be an array');
|
|
452
|
+
}
|
|
453
|
+
if (!Array.isArray(command.arguments)) {
|
|
454
|
+
errors.push('Command arguments must be an array');
|
|
455
|
+
}
|
|
456
|
+
for (const option of command.options) {
|
|
457
|
+
if (!option.name || typeof option.name !== 'string') {
|
|
458
|
+
errors.push(`Option name is required and must be a string`);
|
|
459
|
+
}
|
|
460
|
+
if (!['string', 'number', 'boolean', 'array'].includes(option.type)) {
|
|
461
|
+
errors.push(`Option ${option.name} has invalid type: ${option.type}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const arg of command.arguments) {
|
|
465
|
+
if (!arg.name || typeof arg.name !== 'string') {
|
|
466
|
+
errors.push(`Argument name is required and must be a string`);
|
|
467
|
+
}
|
|
468
|
+
if (!['string', 'number', 'file', 'url'].includes(arg.type)) {
|
|
469
|
+
errors.push(`Argument ${arg.name} has invalid type: ${arg.type}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
valid: errors.length === 0,
|
|
474
|
+
errors,
|
|
475
|
+
warnings: []
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
createParseResult(command, options, errors, success, arguments_ = []) {
|
|
479
|
+
return {
|
|
480
|
+
success,
|
|
481
|
+
command,
|
|
482
|
+
options,
|
|
483
|
+
arguments: arguments_,
|
|
484
|
+
errors,
|
|
485
|
+
warnings: []
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
getCommands() {
|
|
489
|
+
return Array.from(this.commands.values());
|
|
490
|
+
}
|
|
491
|
+
getCommand(name) {
|
|
492
|
+
const resolvedName = this.resolveCommandName(name);
|
|
493
|
+
return this.commands.get(resolvedName);
|
|
494
|
+
}
|
|
495
|
+
hasCommand(name) {
|
|
496
|
+
const resolvedName = this.resolveCommandName(name);
|
|
497
|
+
return this.commands.has(resolvedName);
|
|
498
|
+
}
|
|
499
|
+
generateContextualHelp(error, commandName) {
|
|
500
|
+
return this.helpSystem.generateContextualHelp(error, commandName);
|
|
501
|
+
}
|
|
502
|
+
getHelpSystem() {
|
|
503
|
+
return this.helpSystem;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
exports.ArgumentParser = ArgumentParser;
|