chrome-cdp-cli 1.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/LICENSE +21 -0
- package/README.md +411 -0
- package/dist/cli/CLIApplication.js +92 -0
- package/dist/cli/CLIInterface.js +327 -0
- package/dist/cli/CommandRegistry.js +33 -0
- package/dist/cli/CommandRouter.js +256 -0
- package/dist/cli/index.js +12 -0
- package/dist/client/CDPClient.js +159 -0
- package/dist/client/index.js +17 -0
- package/dist/connection/ConnectionManager.js +164 -0
- package/dist/connection/index.js +5 -0
- package/dist/handlers/EvaluateScriptHandler.js +205 -0
- package/dist/handlers/index.js +17 -0
- package/dist/index.js +44 -0
- package/dist/interfaces/CDPClient.js +2 -0
- package/dist/interfaces/CLIInterface.js +11 -0
- package/dist/interfaces/CommandHandler.js +2 -0
- package/dist/interfaces/ConnectionManager.js +2 -0
- package/dist/interfaces/index.js +20 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/constants.js +31 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/logger.js +44 -0
- package/package.json +81 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CLIInterface = void 0;
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const CLIInterface_1 = require("../interfaces/CLIInterface");
|
|
40
|
+
const CommandRegistry_1 = require("./CommandRegistry");
|
|
41
|
+
const CommandRouter_1 = require("./CommandRouter");
|
|
42
|
+
class CLIInterface {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.program = new commander_1.Command();
|
|
45
|
+
this.registry = new CommandRegistry_1.CommandRegistry();
|
|
46
|
+
this.router = new CommandRouter_1.CommandRouter(this.registry);
|
|
47
|
+
this.setupProgram();
|
|
48
|
+
}
|
|
49
|
+
setupProgram() {
|
|
50
|
+
this.program
|
|
51
|
+
.name('chrome-cli')
|
|
52
|
+
.description('Command-line tool for controlling Chrome browser via DevTools Protocol')
|
|
53
|
+
.version('1.0.0')
|
|
54
|
+
.allowUnknownOption(true)
|
|
55
|
+
.allowExcessArguments(true);
|
|
56
|
+
this.program
|
|
57
|
+
.option('-h, --host <host>', 'Chrome host address', CLIInterface_1.DEFAULT_CLI_CONFIG.host)
|
|
58
|
+
.option('-p, --port <port>', 'DevTools port', (value) => parseInt(value, 10), CLIInterface_1.DEFAULT_CLI_CONFIG.port)
|
|
59
|
+
.option('-f, --format <format>', 'Output format (json|text)', CLIInterface_1.DEFAULT_CLI_CONFIG.outputFormat)
|
|
60
|
+
.option('-v, --verbose', 'Enable verbose logging', CLIInterface_1.DEFAULT_CLI_CONFIG.verbose)
|
|
61
|
+
.option('-q, --quiet', 'Enable quiet mode', CLIInterface_1.DEFAULT_CLI_CONFIG.quiet)
|
|
62
|
+
.option('-t, --timeout <timeout>', 'Command timeout in milliseconds', (value) => parseInt(value, 10), CLIInterface_1.DEFAULT_CLI_CONFIG.timeout)
|
|
63
|
+
.option('-c, --config <config>', 'Configuration file path');
|
|
64
|
+
}
|
|
65
|
+
parseArgs(argv) {
|
|
66
|
+
try {
|
|
67
|
+
const args = argv.slice(2);
|
|
68
|
+
const options = {};
|
|
69
|
+
const commandArgs = [];
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (i < args.length) {
|
|
72
|
+
const arg = args[i];
|
|
73
|
+
if (arg.startsWith('--')) {
|
|
74
|
+
const optionName = arg.substring(2);
|
|
75
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
76
|
+
options[optionName] = args[i + 1];
|
|
77
|
+
i += 2;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
options[optionName] = true;
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (arg.startsWith('-') && arg.length > 1) {
|
|
85
|
+
const shortOption = arg.substring(1);
|
|
86
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
87
|
+
options[shortOption] = args[i + 1];
|
|
88
|
+
i += 2;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
options[shortOption] = true;
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
commandArgs.push(arg);
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (options.h)
|
|
101
|
+
options.host = options.h;
|
|
102
|
+
if (options.p)
|
|
103
|
+
options.port = parseInt(options.p, 10);
|
|
104
|
+
if (options.f)
|
|
105
|
+
options.format = options.f;
|
|
106
|
+
if (options.v)
|
|
107
|
+
options.verbose = true;
|
|
108
|
+
if (options.q)
|
|
109
|
+
options.quiet = true;
|
|
110
|
+
if (options.t)
|
|
111
|
+
options.timeout = parseInt(options.t, 10);
|
|
112
|
+
if (options.c)
|
|
113
|
+
options.config = options.c;
|
|
114
|
+
if (options.port && typeof options.port === 'string') {
|
|
115
|
+
options.port = parseInt(options.port, 10);
|
|
116
|
+
}
|
|
117
|
+
if (options.timeout && typeof options.timeout === 'string') {
|
|
118
|
+
options.timeout = parseInt(options.timeout, 10);
|
|
119
|
+
}
|
|
120
|
+
const command = commandArgs[0] || 'help';
|
|
121
|
+
const remainingArgs = commandArgs.slice(1);
|
|
122
|
+
const normalizedCommand = command.replace(/-/g, '_');
|
|
123
|
+
const config = this.loadConfiguration(options.config, options);
|
|
124
|
+
const parsedArgs = this.extractCommandArgs(normalizedCommand, remainingArgs, options);
|
|
125
|
+
return {
|
|
126
|
+
name: normalizedCommand,
|
|
127
|
+
args: parsedArgs,
|
|
128
|
+
config
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw new Error(`Failed to parse arguments: ${error instanceof Error ? error.message : String(error)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
loadConfiguration(configPath, cliOptions = {}) {
|
|
136
|
+
let fileConfig = {};
|
|
137
|
+
if (configPath) {
|
|
138
|
+
fileConfig = this.loadConfigFile(configPath);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const defaultPaths = [
|
|
142
|
+
'.chrome-cli.json',
|
|
143
|
+
path.join(process.env.HOME || '', '.chrome-cli.json'),
|
|
144
|
+
'/etc/chrome-cli.json'
|
|
145
|
+
];
|
|
146
|
+
for (const defaultPath of defaultPaths) {
|
|
147
|
+
try {
|
|
148
|
+
fileConfig = this.loadConfigFile(defaultPath);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
host: cliOptions.host || fileConfig.host || CLIInterface_1.DEFAULT_CLI_CONFIG.host,
|
|
157
|
+
port: cliOptions.port || fileConfig.port || CLIInterface_1.DEFAULT_CLI_CONFIG.port,
|
|
158
|
+
outputFormat: cliOptions.format || fileConfig.outputFormat || CLIInterface_1.DEFAULT_CLI_CONFIG.outputFormat,
|
|
159
|
+
verbose: cliOptions.verbose || fileConfig.verbose || CLIInterface_1.DEFAULT_CLI_CONFIG.verbose,
|
|
160
|
+
quiet: cliOptions.quiet || fileConfig.quiet || CLIInterface_1.DEFAULT_CLI_CONFIG.quiet,
|
|
161
|
+
timeout: cliOptions.timeout || fileConfig.timeout || CLIInterface_1.DEFAULT_CLI_CONFIG.timeout
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
loadConfigFile(configPath) {
|
|
165
|
+
try {
|
|
166
|
+
const content = require('fs').readFileSync(configPath, 'utf-8');
|
|
167
|
+
const config = JSON.parse(content);
|
|
168
|
+
this.validateConfig(config);
|
|
169
|
+
return config;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
throw new Error(`Failed to load config file "${configPath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
validateConfig(config) {
|
|
176
|
+
if (typeof config !== 'object' || config === null) {
|
|
177
|
+
throw new Error('Configuration must be a JSON object');
|
|
178
|
+
}
|
|
179
|
+
if (config.host !== undefined && typeof config.host !== 'string') {
|
|
180
|
+
throw new Error('Configuration "host" must be a string');
|
|
181
|
+
}
|
|
182
|
+
if (config.port !== undefined && (typeof config.port !== 'number' || config.port < 1 || config.port > 65535)) {
|
|
183
|
+
throw new Error('Configuration "port" must be a number between 1 and 65535');
|
|
184
|
+
}
|
|
185
|
+
if (config.outputFormat !== undefined && !['json', 'text'].includes(config.outputFormat)) {
|
|
186
|
+
throw new Error('Configuration "outputFormat" must be "json" or "text"');
|
|
187
|
+
}
|
|
188
|
+
if (config.verbose !== undefined && typeof config.verbose !== 'boolean') {
|
|
189
|
+
throw new Error('Configuration "verbose" must be a boolean');
|
|
190
|
+
}
|
|
191
|
+
if (config.quiet !== undefined && typeof config.quiet !== 'boolean') {
|
|
192
|
+
throw new Error('Configuration "quiet" must be a boolean');
|
|
193
|
+
}
|
|
194
|
+
if (config.timeout !== undefined && (typeof config.timeout !== 'number' || config.timeout < 1)) {
|
|
195
|
+
throw new Error('Configuration "timeout" must be a positive number');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
extractCommandArgs(command, args, options) {
|
|
199
|
+
const commandArgs = {};
|
|
200
|
+
const normalizedCommand = command.replace(/-/g, '_');
|
|
201
|
+
switch (normalizedCommand) {
|
|
202
|
+
case 'navigate':
|
|
203
|
+
commandArgs.url = args[0];
|
|
204
|
+
break;
|
|
205
|
+
case 'new_page':
|
|
206
|
+
if (options.url)
|
|
207
|
+
commandArgs.url = options.url;
|
|
208
|
+
break;
|
|
209
|
+
case 'close_page':
|
|
210
|
+
if (options['page-id'])
|
|
211
|
+
commandArgs.pageId = options['page-id'];
|
|
212
|
+
break;
|
|
213
|
+
case 'select_page':
|
|
214
|
+
commandArgs.pageId = args[0];
|
|
215
|
+
break;
|
|
216
|
+
case 'resize_page':
|
|
217
|
+
commandArgs.width = parseInt(args[0], 10);
|
|
218
|
+
commandArgs.height = parseInt(args[1], 10);
|
|
219
|
+
break;
|
|
220
|
+
case 'eval':
|
|
221
|
+
if (args[0] && !options.expression && !options.e && !options.file && !options.f) {
|
|
222
|
+
commandArgs.expression = args[0];
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
if (options.expression || options.e)
|
|
226
|
+
commandArgs.expression = options.expression || options.e;
|
|
227
|
+
if (options.file || options.f)
|
|
228
|
+
commandArgs.file = options.file || options.f;
|
|
229
|
+
}
|
|
230
|
+
commandArgs.awaitPromise = options['await-promise'] !== false;
|
|
231
|
+
commandArgs.returnByValue = options['return-by-value'] !== false;
|
|
232
|
+
break;
|
|
233
|
+
case 'click':
|
|
234
|
+
case 'hover':
|
|
235
|
+
commandArgs.selector = args[0];
|
|
236
|
+
break;
|
|
237
|
+
case 'fill':
|
|
238
|
+
commandArgs.selector = args[0];
|
|
239
|
+
commandArgs.text = args[1];
|
|
240
|
+
break;
|
|
241
|
+
case 'screenshot':
|
|
242
|
+
if (options.output || options.o)
|
|
243
|
+
commandArgs.output = options.output || options.o;
|
|
244
|
+
if (options.width || options.w)
|
|
245
|
+
commandArgs.width = parseInt(options.width || options.w, 10);
|
|
246
|
+
if (options.height || options.h)
|
|
247
|
+
commandArgs.height = parseInt(options.height || options.h, 10);
|
|
248
|
+
break;
|
|
249
|
+
case 'console_messages':
|
|
250
|
+
if (options.filter)
|
|
251
|
+
commandArgs.filter = options.filter;
|
|
252
|
+
break;
|
|
253
|
+
case 'network_requests':
|
|
254
|
+
if (options.filter)
|
|
255
|
+
commandArgs.filter = options.filter;
|
|
256
|
+
break;
|
|
257
|
+
case 'help':
|
|
258
|
+
if (args[0])
|
|
259
|
+
commandArgs.command = args[0];
|
|
260
|
+
break;
|
|
261
|
+
default:
|
|
262
|
+
args.forEach((arg, index) => {
|
|
263
|
+
commandArgs[`arg${index}`] = arg;
|
|
264
|
+
});
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
return commandArgs;
|
|
268
|
+
}
|
|
269
|
+
async execute(command) {
|
|
270
|
+
try {
|
|
271
|
+
return await this.router.execute(command);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
277
|
+
exitCode: 1
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
formatOutput(result, format) {
|
|
282
|
+
if (format === 'json') {
|
|
283
|
+
return JSON.stringify(result, null, 2);
|
|
284
|
+
}
|
|
285
|
+
if (!result.success) {
|
|
286
|
+
return `Error: ${result.error}`;
|
|
287
|
+
}
|
|
288
|
+
if (result.data === undefined || result.data === null) {
|
|
289
|
+
return 'Success';
|
|
290
|
+
}
|
|
291
|
+
if (typeof result.data === 'string') {
|
|
292
|
+
return result.data;
|
|
293
|
+
}
|
|
294
|
+
if (typeof result.data === 'object') {
|
|
295
|
+
return JSON.stringify(result.data, null, 2);
|
|
296
|
+
}
|
|
297
|
+
return String(result.data);
|
|
298
|
+
}
|
|
299
|
+
showHelp(commandName) {
|
|
300
|
+
if (commandName) {
|
|
301
|
+
const handler = this.registry.get(commandName);
|
|
302
|
+
if (handler && handler.getHelp) {
|
|
303
|
+
return handler.getHelp();
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
return `Unknown command: ${commandName}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return this.program.helpInformation();
|
|
310
|
+
}
|
|
311
|
+
getAvailableCommands() {
|
|
312
|
+
return this.registry.getCommandNames();
|
|
313
|
+
}
|
|
314
|
+
registerHandler(handler) {
|
|
315
|
+
this.registry.register(handler);
|
|
316
|
+
}
|
|
317
|
+
setClient(client) {
|
|
318
|
+
this.router.setClient(client);
|
|
319
|
+
}
|
|
320
|
+
getRegistry() {
|
|
321
|
+
return this.registry;
|
|
322
|
+
}
|
|
323
|
+
getRouter() {
|
|
324
|
+
return this.router;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
exports.CLIInterface = CLIInterface;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommandRegistry = void 0;
|
|
4
|
+
class CommandRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.handlers = new Map();
|
|
7
|
+
}
|
|
8
|
+
register(handler) {
|
|
9
|
+
if (this.handlers.has(handler.name)) {
|
|
10
|
+
throw new Error(`Command handler "${handler.name}" is already registered`);
|
|
11
|
+
}
|
|
12
|
+
this.handlers.set(handler.name, handler);
|
|
13
|
+
}
|
|
14
|
+
get(name) {
|
|
15
|
+
return this.handlers.get(name);
|
|
16
|
+
}
|
|
17
|
+
getCommandNames() {
|
|
18
|
+
return Array.from(this.handlers.keys()).sort();
|
|
19
|
+
}
|
|
20
|
+
has(name) {
|
|
21
|
+
return this.handlers.has(name);
|
|
22
|
+
}
|
|
23
|
+
unregister(name) {
|
|
24
|
+
return this.handlers.delete(name);
|
|
25
|
+
}
|
|
26
|
+
clear() {
|
|
27
|
+
this.handlers.clear();
|
|
28
|
+
}
|
|
29
|
+
size() {
|
|
30
|
+
return this.handlers.size;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.CommandRegistry = CommandRegistry;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommandRouter = exports.ExitCode = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
var ExitCode;
|
|
6
|
+
(function (ExitCode) {
|
|
7
|
+
ExitCode[ExitCode["SUCCESS"] = 0] = "SUCCESS";
|
|
8
|
+
ExitCode[ExitCode["GENERAL_ERROR"] = 1] = "GENERAL_ERROR";
|
|
9
|
+
ExitCode[ExitCode["INVALID_COMMAND"] = 2] = "INVALID_COMMAND";
|
|
10
|
+
ExitCode[ExitCode["CONNECTION_ERROR"] = 3] = "CONNECTION_ERROR";
|
|
11
|
+
ExitCode[ExitCode["TIMEOUT_ERROR"] = 4] = "TIMEOUT_ERROR";
|
|
12
|
+
ExitCode[ExitCode["VALIDATION_ERROR"] = 5] = "VALIDATION_ERROR";
|
|
13
|
+
ExitCode[ExitCode["FILE_ERROR"] = 6] = "FILE_ERROR";
|
|
14
|
+
})(ExitCode || (exports.ExitCode = ExitCode = {}));
|
|
15
|
+
class CommandRouter {
|
|
16
|
+
constructor(registry) {
|
|
17
|
+
this.registry = registry;
|
|
18
|
+
this.logger = new logger_1.Logger();
|
|
19
|
+
}
|
|
20
|
+
setClient(client) {
|
|
21
|
+
this.client = client;
|
|
22
|
+
}
|
|
23
|
+
async execute(command) {
|
|
24
|
+
try {
|
|
25
|
+
if (this.isSpecialCommand(command.name)) {
|
|
26
|
+
return await this.executeSpecialCommand(command);
|
|
27
|
+
}
|
|
28
|
+
if (!this.client) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
error: 'Not connected to Chrome. Use "connect" command first.',
|
|
32
|
+
exitCode: ExitCode.CONNECTION_ERROR
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const handler = this.registry.get(command.name);
|
|
36
|
+
if (!handler) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: `Unknown command: ${command.name}. Use "help" to see available commands.`,
|
|
40
|
+
exitCode: ExitCode.INVALID_COMMAND
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (handler.validateArgs && !handler.validateArgs(command.args)) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: `Invalid arguments for command "${command.name}". Use "help ${command.name}" for usage information.`,
|
|
47
|
+
exitCode: ExitCode.VALIDATION_ERROR
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (command.config.verbose) {
|
|
51
|
+
this.logger.info(`Executing command: ${command.name}`, command.args);
|
|
52
|
+
}
|
|
53
|
+
const result = await this.executeWithTimeout(handler, command);
|
|
54
|
+
if (command.config.verbose) {
|
|
55
|
+
this.logger.info(`Command result:`, result);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
61
|
+
if (!command.config.quiet) {
|
|
62
|
+
this.logger.error(`Command execution failed: ${errorMessage}`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: errorMessage,
|
|
67
|
+
exitCode: this.getExitCodeForError(error)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
isSpecialCommand(commandName) {
|
|
72
|
+
const specialCommands = ['help', 'connect', 'disconnect'];
|
|
73
|
+
return specialCommands.includes(commandName);
|
|
74
|
+
}
|
|
75
|
+
async executeSpecialCommand(command) {
|
|
76
|
+
switch (command.name) {
|
|
77
|
+
case 'help':
|
|
78
|
+
return this.executeHelpCommand(command);
|
|
79
|
+
case 'connect':
|
|
80
|
+
return this.executeConnectCommand(command);
|
|
81
|
+
case 'disconnect':
|
|
82
|
+
return this.executeDisconnectCommand();
|
|
83
|
+
default:
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: `Unknown special command: ${command.name}`,
|
|
87
|
+
exitCode: ExitCode.INVALID_COMMAND
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
executeHelpCommand(command) {
|
|
92
|
+
const commandName = command.args.command;
|
|
93
|
+
if (commandName) {
|
|
94
|
+
const normalizedCommandName = commandName.replace(/-/g, '_');
|
|
95
|
+
const handler = this.registry.get(normalizedCommandName);
|
|
96
|
+
if (!handler) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: `Unknown command: ${commandName}`,
|
|
100
|
+
exitCode: ExitCode.INVALID_COMMAND
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const helpText = handler.getHelp ? handler.getHelp() : `No help available for command: ${commandName}`;
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
data: helpText
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const commands = this.registry.getCommandNames();
|
|
111
|
+
const helpText = this.generateGeneralHelp(commands);
|
|
112
|
+
return {
|
|
113
|
+
success: true,
|
|
114
|
+
data: helpText
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async executeConnectCommand(command) {
|
|
119
|
+
try {
|
|
120
|
+
return {
|
|
121
|
+
success: true,
|
|
122
|
+
data: `Connected to Chrome at ${command.config.host}:${command.config.port}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: `Failed to connect: ${error instanceof Error ? error.message : String(error)}`,
|
|
129
|
+
exitCode: ExitCode.CONNECTION_ERROR
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async executeDisconnectCommand() {
|
|
134
|
+
try {
|
|
135
|
+
if (this.client) {
|
|
136
|
+
await this.client.disconnect();
|
|
137
|
+
this.client = undefined;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
data: 'Disconnected from Chrome'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
error: `Failed to disconnect: ${error instanceof Error ? error.message : String(error)}`,
|
|
148
|
+
exitCode: ExitCode.CONNECTION_ERROR
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async executeWithTimeout(handler, command) {
|
|
153
|
+
const timeout = command.config.timeout;
|
|
154
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
reject(new Error(`Command timeout after ${timeout}ms`));
|
|
157
|
+
}, timeout);
|
|
158
|
+
});
|
|
159
|
+
const executionPromise = handler.execute(this.client, command.args);
|
|
160
|
+
try {
|
|
161
|
+
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
162
|
+
if (!result || typeof result !== 'object') {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error: 'Invalid command result format',
|
|
166
|
+
exitCode: ExitCode.GENERAL_ERROR
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (result.success && !result.exitCode) {
|
|
170
|
+
result.exitCode = ExitCode.SUCCESS;
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error instanceof Error && error.message.includes('timeout')) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
error: error.message,
|
|
179
|
+
exitCode: ExitCode.TIMEOUT_ERROR
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
generateGeneralHelp(commands) {
|
|
186
|
+
return `
|
|
187
|
+
Chrome DevTools CLI - Command-line tool for controlling Chrome browser
|
|
188
|
+
|
|
189
|
+
Usage: chrome-cli [options] <command> [command-options]
|
|
190
|
+
|
|
191
|
+
Global Options:
|
|
192
|
+
-h, --host <host> Chrome host address (default: localhost)
|
|
193
|
+
-p, --port <port> DevTools port (default: 9222)
|
|
194
|
+
-f, --format <format> Output format: json|text (default: text)
|
|
195
|
+
-v, --verbose Enable verbose logging
|
|
196
|
+
-q, --quiet Enable quiet mode
|
|
197
|
+
-t, --timeout <ms> Command timeout in milliseconds (default: 30000)
|
|
198
|
+
-c, --config <path> Configuration file path
|
|
199
|
+
|
|
200
|
+
Available Commands:
|
|
201
|
+
${commands.map(cmd => ` ${cmd.padEnd(20)} - ${this.getCommandDescription(cmd)}`).join('\n')}
|
|
202
|
+
|
|
203
|
+
Examples:
|
|
204
|
+
chrome-cli eval "document.title"
|
|
205
|
+
chrome-cli eval --file script.js
|
|
206
|
+
chrome-cli help <command>
|
|
207
|
+
|
|
208
|
+
For more information about a specific command, use:
|
|
209
|
+
chrome-cli help <command>
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
getCommandDescription(commandName) {
|
|
213
|
+
const descriptions = {
|
|
214
|
+
'connect': 'Connect to Chrome instance',
|
|
215
|
+
'disconnect': 'Disconnect from Chrome instance',
|
|
216
|
+
'navigate': 'Navigate to URL',
|
|
217
|
+
'new-page': 'Create new page/tab',
|
|
218
|
+
'close-page': 'Close current or specified page',
|
|
219
|
+
'list-pages': 'List all open pages',
|
|
220
|
+
'select-page': 'Select/focus page',
|
|
221
|
+
'resize-page': 'Resize browser viewport',
|
|
222
|
+
'eval': 'Execute JavaScript code',
|
|
223
|
+
'click': 'Click element',
|
|
224
|
+
'fill': 'Fill form field',
|
|
225
|
+
'hover': 'Hover over element',
|
|
226
|
+
'screenshot': 'Take screenshot',
|
|
227
|
+
'get-html': 'Get page HTML content',
|
|
228
|
+
'console-messages': 'Get console messages',
|
|
229
|
+
'network-requests': 'Get network requests',
|
|
230
|
+
'help': 'Show help information'
|
|
231
|
+
};
|
|
232
|
+
return descriptions[commandName] || 'No description available';
|
|
233
|
+
}
|
|
234
|
+
getExitCodeForError(error) {
|
|
235
|
+
if (error instanceof Error) {
|
|
236
|
+
const message = error.message.toLowerCase();
|
|
237
|
+
if (message.includes('timeout')) {
|
|
238
|
+
return ExitCode.TIMEOUT_ERROR;
|
|
239
|
+
}
|
|
240
|
+
if (message.includes('connection') || message.includes('connect')) {
|
|
241
|
+
return ExitCode.CONNECTION_ERROR;
|
|
242
|
+
}
|
|
243
|
+
if (message.includes('file') || message.includes('path')) {
|
|
244
|
+
return ExitCode.FILE_ERROR;
|
|
245
|
+
}
|
|
246
|
+
if (message.includes('invalid') || message.includes('validation')) {
|
|
247
|
+
return ExitCode.VALIDATION_ERROR;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return ExitCode.GENERAL_ERROR;
|
|
251
|
+
}
|
|
252
|
+
getRegistry() {
|
|
253
|
+
return this.registry;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.CommandRouter = CommandRouter;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLIApplication = exports.ExitCode = exports.CommandRouter = exports.CommandRegistry = exports.CLIInterface = void 0;
|
|
4
|
+
var CLIInterface_1 = require("./CLIInterface");
|
|
5
|
+
Object.defineProperty(exports, "CLIInterface", { enumerable: true, get: function () { return CLIInterface_1.CLIInterface; } });
|
|
6
|
+
var CommandRegistry_1 = require("./CommandRegistry");
|
|
7
|
+
Object.defineProperty(exports, "CommandRegistry", { enumerable: true, get: function () { return CommandRegistry_1.CommandRegistry; } });
|
|
8
|
+
var CommandRouter_1 = require("./CommandRouter");
|
|
9
|
+
Object.defineProperty(exports, "CommandRouter", { enumerable: true, get: function () { return CommandRouter_1.CommandRouter; } });
|
|
10
|
+
Object.defineProperty(exports, "ExitCode", { enumerable: true, get: function () { return CommandRouter_1.ExitCode; } });
|
|
11
|
+
var CLIApplication_1 = require("./CLIApplication");
|
|
12
|
+
Object.defineProperty(exports, "CLIApplication", { enumerable: true, get: function () { return CLIApplication_1.CLIApplication; } });
|