chrome-cdp-cli 2.0.5 → 2.1.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/dist/cli/ArgumentParser.js +172 -92
- package/dist/cli/CLIApplication.js +155 -82
- package/dist/cli/CommandRouter.js +98 -74
- package/dist/cli/CommandSchemaRegistry.js +506 -398
- package/dist/cli/HelpSystem.js +286 -256
- package/dist/handlers/ClickHandler.js +91 -27
- package/dist/handlers/InstallClaudeSkillHandler.js +220 -220
- package/dist/handlers/InstallCursorCommandHandler.js +60 -60
- package/dist/handlers/ListConsoleMessagesHandler.js +126 -178
- package/dist/handlers/ListNetworkRequestsHandler.js +128 -108
- package/dist/handlers/RestartProxyHandler.js +4 -4
- package/dist/handlers/TakeScreenshotHandler.js +70 -59
- package/dist/handlers/TakeSnapshotHandler.js +223 -165
- package/dist/handlers/index.js +0 -1
- package/dist/monitors/ConsoleMonitor.js +29 -0
- package/dist/monitors/NetworkMonitor.js +43 -19
- package/dist/proxy/server/CDPProxyServer.js +5 -1
- package/dist/proxy/server/CommandExecutionService.js +1 -1
- package/dist/proxy/server/ProxyAPIServer.js +11 -6
- package/package.json +3 -2
|
@@ -7,7 +7,6 @@ const ConnectionManager_1 = require("../connection/ConnectionManager");
|
|
|
7
7
|
const handlers_1 = require("../handlers");
|
|
8
8
|
const logger_1 = require("../utils/logger");
|
|
9
9
|
const CommandRouter_1 = require("./CommandRouter");
|
|
10
|
-
const ProxyManager_1 = require("../proxy/ProxyManager");
|
|
11
10
|
const OutputManager_1 = require("./OutputManager");
|
|
12
11
|
class CLIApplication {
|
|
13
12
|
constructor() {
|
|
@@ -16,7 +15,6 @@ class CLIApplication {
|
|
|
16
15
|
this.connectionManager = new ConnectionManager_1.ConnectionManager();
|
|
17
16
|
this.outputManager = new OutputManager_1.OutputManager();
|
|
18
17
|
this.logger = new logger_1.Logger();
|
|
19
|
-
this.proxyManager = ProxyManager_1.ProxyManager.getInstance();
|
|
20
18
|
this.setupHandlers();
|
|
21
19
|
}
|
|
22
20
|
setupHandlers() {
|
|
@@ -36,14 +34,13 @@ class CLIApplication {
|
|
|
36
34
|
this.cli.registerHandler(new handlers_1.UploadFileHandler());
|
|
37
35
|
this.cli.registerHandler(new handlers_1.WaitForHandler());
|
|
38
36
|
this.cli.registerHandler(new handlers_1.HandleDialogHandler());
|
|
39
|
-
this.cli.registerHandler(new handlers_1.RestartProxyHandler());
|
|
40
37
|
}
|
|
41
38
|
configureDebugMode(debug) {
|
|
42
39
|
const registry = this.cli.getRegistry();
|
|
43
40
|
const handlerNames = registry.getCommandNames();
|
|
44
41
|
for (const handlerName of handlerNames) {
|
|
45
42
|
const handler = registry.get(handlerName);
|
|
46
|
-
if (handler && typeof handler.setDebug ===
|
|
43
|
+
if (handler && typeof handler.setDebug === "function") {
|
|
47
44
|
handler.setDebug(debug);
|
|
48
45
|
}
|
|
49
46
|
}
|
|
@@ -56,10 +53,10 @@ class CLIApplication {
|
|
|
56
53
|
}
|
|
57
54
|
catch (parseError) {
|
|
58
55
|
if (parseError instanceof Error) {
|
|
59
|
-
if (parseError.message ===
|
|
56
|
+
if (parseError.message === "VERSION_COMMAND_EXECUTED") {
|
|
60
57
|
return CommandRouter_1.ExitCode.SUCCESS;
|
|
61
58
|
}
|
|
62
|
-
if (parseError.message ===
|
|
59
|
+
if (parseError.message === "HELP_COMMAND_EXECUTED") {
|
|
63
60
|
return CommandRouter_1.ExitCode.SUCCESS;
|
|
64
61
|
}
|
|
65
62
|
}
|
|
@@ -85,73 +82,147 @@ class CLIApplication {
|
|
|
85
82
|
this.logger.setLevel(2);
|
|
86
83
|
}
|
|
87
84
|
this.configureDebugMode(command.config.debug);
|
|
88
|
-
this.logger.debug(
|
|
89
|
-
this.logger.debug(
|
|
90
|
-
if (command.config.verbose) {
|
|
91
|
-
this.proxyManager.setLogging(true);
|
|
92
|
-
}
|
|
93
|
-
this.logger.debug('Ensuring proxy is ready...');
|
|
94
|
-
await this.ensureProxyReady();
|
|
85
|
+
this.logger.debug("CLIApplication.run called with argv:", argv);
|
|
86
|
+
this.logger.debug("Parsed command:", command);
|
|
95
87
|
if (this.needsConnection(command.name)) {
|
|
96
|
-
this.logger.debug(
|
|
88
|
+
this.logger.debug("Command needs connection, ensuring connection...");
|
|
97
89
|
try {
|
|
98
90
|
await this.ensureConnection(command);
|
|
99
91
|
}
|
|
100
92
|
catch (connectionError) {
|
|
101
|
-
|
|
93
|
+
const msg = connectionError instanceof Error
|
|
94
|
+
? connectionError.message
|
|
95
|
+
: String(connectionError);
|
|
96
|
+
if (msg === "Cancelled") {
|
|
97
|
+
return CommandRouter_1.ExitCode.SUCCESS;
|
|
98
|
+
}
|
|
99
|
+
process.stderr.write(`\nError: ${msg}\n`);
|
|
100
|
+
process.stderr.write("\nTip: Launch Chrome with remote debugging enabled:\n" +
|
|
101
|
+
" chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug\n\n" +
|
|
102
|
+
"From Chrome 136, --user-data-dir is required. See:\n" +
|
|
103
|
+
" https://developer.chrome.com/blog/remote-debugging-port\n\n");
|
|
104
|
+
return CommandRouter_1.ExitCode.CONNECTION_ERROR;
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
|
-
this.logger.debug(
|
|
107
|
+
this.logger.debug("Executing command via CLI interface...");
|
|
105
108
|
const result = await this.cli.execute(command);
|
|
106
|
-
this.logger.debug(
|
|
109
|
+
this.logger.debug("Command execution result:", result);
|
|
107
110
|
this.outputResult(result, command);
|
|
108
|
-
return result.exitCode ||
|
|
111
|
+
return (result.exitCode ||
|
|
112
|
+
(result.success ? CommandRouter_1.ExitCode.SUCCESS : CommandRouter_1.ExitCode.GENERAL_ERROR));
|
|
109
113
|
}
|
|
110
114
|
catch (error) {
|
|
111
|
-
this.logger.debug(
|
|
115
|
+
this.logger.debug("Error in CLIApplication.run:", error);
|
|
112
116
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
113
|
-
console.error(this.formatError(errorMessage, {
|
|
117
|
+
console.error(this.formatError(errorMessage, {
|
|
118
|
+
outputFormat: "text",
|
|
119
|
+
verbose: false,
|
|
120
|
+
quiet: false,
|
|
121
|
+
debug: false,
|
|
122
|
+
host: "localhost",
|
|
123
|
+
port: 9222,
|
|
124
|
+
timeout: 30000,
|
|
125
|
+
}));
|
|
114
126
|
return CommandRouter_1.ExitCode.GENERAL_ERROR;
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
|
-
async ensureProxyReady() {
|
|
118
|
-
try {
|
|
119
|
-
const isReady = await this.proxyManager.ensureProxyReady();
|
|
120
|
-
if (!isReady) {
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
129
|
needsConnection(commandName) {
|
|
127
130
|
const noConnectionCommands = [
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
"help",
|
|
132
|
+
"connect",
|
|
133
|
+
"disconnect",
|
|
134
|
+
"install_cursor_command",
|
|
135
|
+
"install_claude_skill",
|
|
133
136
|
];
|
|
134
137
|
return !noConnectionCommands.includes(commandName);
|
|
135
138
|
}
|
|
136
139
|
isDevToolsWindow(target) {
|
|
137
140
|
const url = target.url.toLowerCase();
|
|
138
141
|
const title = target.title.toLowerCase();
|
|
139
|
-
return url.startsWith(
|
|
140
|
-
url.startsWith(
|
|
141
|
-
title.includes(
|
|
142
|
-
title.includes(
|
|
142
|
+
return (url.startsWith("chrome-devtools://") ||
|
|
143
|
+
url.startsWith("devtools://") ||
|
|
144
|
+
title.includes("devtools") ||
|
|
145
|
+
title.includes("chrome devtools"));
|
|
143
146
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
async selectTargetInteractive(targets) {
|
|
148
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
149
|
+
process.stderr.write("\nAvailable Chrome pages (excluding DevTools windows):\n");
|
|
150
|
+
targets.forEach((t, i) => {
|
|
151
|
+
const url = t.url.length > 70 ? t.url.substring(0, 67) + "..." : t.url;
|
|
152
|
+
process.stderr.write(` [${i + 1}] ${t.title || "(Untitled)"}\n ${url}\n`);
|
|
153
|
+
});
|
|
154
|
+
process.stderr.write(`\nTip: re-run with cdp -i <number> <command>\n\n`);
|
|
155
|
+
throw new Error(`Multiple Chrome pages found (${targets.length}). Use -i <number> to pick one.`);
|
|
156
|
+
}
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
let cursor = 0;
|
|
159
|
+
const RESET = "\x1b[0m";
|
|
160
|
+
const BOLD = "\x1b[1m";
|
|
161
|
+
const CYAN = "\x1b[36m";
|
|
162
|
+
const DIM = "\x1b[2m";
|
|
163
|
+
const CLEAR_LINE = "\x1b[2K\x1b[G";
|
|
164
|
+
const SAVE = "\x1b[s";
|
|
165
|
+
const RESTORE = "\x1b[u";
|
|
166
|
+
const truncate = (s, max) => s.length > max ? s.substring(0, max - 1) + "…" : s;
|
|
167
|
+
const render = (first) => {
|
|
168
|
+
if (first) {
|
|
169
|
+
process.stderr.write(SAVE);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
process.stderr.write(RESTORE);
|
|
173
|
+
}
|
|
174
|
+
process.stderr.write(`${CLEAR_LINE}${BOLD}Select a Chrome page${RESET} (↑↓ navigate, Enter select, q quit)\n`);
|
|
175
|
+
process.stderr.write(`${CLEAR_LINE}${"─".repeat(54)}\n`);
|
|
176
|
+
targets.forEach((t, i) => {
|
|
177
|
+
const num = `[${i + 1}]`;
|
|
178
|
+
const rawTitle = t.title || "(Untitled)";
|
|
179
|
+
const title = truncate(rawTitle, 52);
|
|
180
|
+
const url = truncate(t.url, 60);
|
|
181
|
+
const selected = i === cursor;
|
|
182
|
+
if (selected) {
|
|
183
|
+
process.stderr.write(`${CLEAR_LINE}${CYAN}${BOLD} ❯ ${num} ${title}${RESET}\n`);
|
|
184
|
+
process.stderr.write(`${CLEAR_LINE}${CYAN} ${url}${RESET}\n`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
process.stderr.write(`${CLEAR_LINE} ${num} ${title}\n`);
|
|
188
|
+
process.stderr.write(`${CLEAR_LINE}${DIM} ${url}${RESET}\n`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
process.stderr.write(`${CLEAR_LINE}${"─".repeat(54)}\n`);
|
|
192
|
+
process.stderr.write(`${CLEAR_LINE}${DIM}Tip: skip this prompt with cdp -i <number> <command>${RESET}\n` +
|
|
193
|
+
`${CLEAR_LINE}${DIM} or close other tabs until only one remains.${RESET}\n`);
|
|
194
|
+
};
|
|
195
|
+
render(true);
|
|
196
|
+
process.stdin.setRawMode(true);
|
|
197
|
+
process.stdin.resume();
|
|
198
|
+
process.stdin.setEncoding("utf8");
|
|
199
|
+
const onKey = (key) => {
|
|
200
|
+
if (key === "\x1b[A") {
|
|
201
|
+
cursor = (cursor - 1 + targets.length) % targets.length;
|
|
202
|
+
render(false);
|
|
203
|
+
}
|
|
204
|
+
else if (key === "\x1b[B") {
|
|
205
|
+
cursor = (cursor + 1) % targets.length;
|
|
206
|
+
render(false);
|
|
207
|
+
}
|
|
208
|
+
else if (key === "\r" || key === "\n") {
|
|
209
|
+
cleanup();
|
|
210
|
+
process.stderr.write("\n");
|
|
211
|
+
resolve(targets[cursor]);
|
|
212
|
+
}
|
|
213
|
+
else if (key === "q" || key === "\x03" || key === "\x1b") {
|
|
214
|
+
cleanup();
|
|
215
|
+
process.stderr.write("\n");
|
|
216
|
+
reject(new Error("Cancelled"));
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const cleanup = () => {
|
|
220
|
+
process.stdin.removeListener("data", onKey);
|
|
221
|
+
process.stdin.setRawMode(false);
|
|
222
|
+
process.stdin.pause();
|
|
223
|
+
};
|
|
224
|
+
process.stdin.on("data", onKey);
|
|
150
225
|
});
|
|
151
|
-
console.log('\nOptions:');
|
|
152
|
-
console.log(' 1. Use --target-index <number> to select a specific page');
|
|
153
|
-
console.log(' Example: chrome-cdp-cli --target-index 1 eval "document.title"');
|
|
154
|
-
console.log(' 2. Close other pages until only one page remains\n');
|
|
155
226
|
}
|
|
156
227
|
async ensureConnection(command) {
|
|
157
228
|
if (this.client) {
|
|
@@ -161,47 +232,55 @@ class CLIApplication {
|
|
|
161
232
|
const targets = await this.connectionManager.discoverTargets(command.config.host, command.config.port);
|
|
162
233
|
if (targets.length === 0) {
|
|
163
234
|
throw new Error(`No Chrome targets found at ${command.config.host}:${command.config.port}. ` +
|
|
164
|
-
|
|
235
|
+
"Make sure Chrome is running with --remote-debugging-port=9222");
|
|
165
236
|
}
|
|
166
|
-
const pageTargets = targets.filter(target => target.type ===
|
|
167
|
-
const nonDevToolsTargets = pageTargets.filter(target => !this.isDevToolsWindow(target));
|
|
237
|
+
const pageTargets = targets.filter((target) => target.type === "page");
|
|
238
|
+
const nonDevToolsTargets = pageTargets.filter((target) => !this.isDevToolsWindow(target));
|
|
168
239
|
if (nonDevToolsTargets.length === 0) {
|
|
169
|
-
throw new Error(
|
|
240
|
+
throw new Error("No page targets available (excluding DevTools windows). Open a tab in Chrome.");
|
|
170
241
|
}
|
|
171
242
|
let selectedTarget;
|
|
172
243
|
if (command.config.targetIndex !== undefined) {
|
|
173
244
|
const index = command.config.targetIndex - 1;
|
|
174
245
|
if (index < 0 || index >= nonDevToolsTargets.length) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`
|
|
246
|
+
process.stderr.write("\nAvailable Chrome pages:\n");
|
|
247
|
+
nonDevToolsTargets.forEach((t, i) => {
|
|
248
|
+
process.stderr.write(` [${i + 1}] ${t.title || "(Untitled)"}\n`);
|
|
249
|
+
});
|
|
250
|
+
throw new Error(`Invalid -i value: ${command.config.targetIndex}. ` +
|
|
251
|
+
`Choose a number between 1 and ${nonDevToolsTargets.length}.`);
|
|
178
252
|
}
|
|
179
253
|
selectedTarget = nonDevToolsTargets[index];
|
|
180
254
|
}
|
|
255
|
+
else if (nonDevToolsTargets.length === 1) {
|
|
256
|
+
selectedTarget = nonDevToolsTargets[0];
|
|
257
|
+
}
|
|
181
258
|
else {
|
|
182
|
-
|
|
183
|
-
selectedTarget = nonDevToolsTargets[0];
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
this.displayAvailableTargets(nonDevToolsTargets);
|
|
187
|
-
throw new Error(`Multiple Chrome pages found (${nonDevToolsTargets.length}). ` +
|
|
188
|
-
'Please specify --target-index <number> to select a page, or close other pages until only one remains.');
|
|
189
|
-
}
|
|
259
|
+
selectedTarget = await this.selectTargetInteractive(nonDevToolsTargets);
|
|
190
260
|
}
|
|
191
|
-
this.client = await this.connectionManager.connectToTarget(selectedTarget);
|
|
261
|
+
this.client = (await this.connectionManager.connectToTarget(selectedTarget));
|
|
192
262
|
this.cli.setClient(this.client);
|
|
193
263
|
if (command.config.verbose) {
|
|
194
264
|
this.logger.info(`Connected to Chrome target: ${selectedTarget.title} (${selectedTarget.url})`);
|
|
195
265
|
}
|
|
196
266
|
}
|
|
197
267
|
catch (error) {
|
|
198
|
-
|
|
268
|
+
if (error instanceof Error) {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
throw new Error(String(error));
|
|
199
272
|
}
|
|
200
273
|
}
|
|
201
274
|
outputResult(result, command) {
|
|
275
|
+
if (result._rawOutput === true) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
202
278
|
if (command.config.quiet && result.success) {
|
|
203
279
|
return;
|
|
204
280
|
}
|
|
281
|
+
if (result.isLongRunning === true) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
205
284
|
const output = this.cli.formatOutput(result, command.config.outputFormat);
|
|
206
285
|
if (result.success) {
|
|
207
286
|
console.log(output);
|
|
@@ -215,46 +294,46 @@ class CLIApplication {
|
|
|
215
294
|
const args = argv.slice(2);
|
|
216
295
|
for (let i = 0; i < args.length; i++) {
|
|
217
296
|
const arg = args[i];
|
|
218
|
-
if (arg ===
|
|
297
|
+
if (arg === "--config" || arg === "-c") {
|
|
219
298
|
if (i + 1 < args.length) {
|
|
220
299
|
options.configFile = args[i + 1];
|
|
221
300
|
}
|
|
222
301
|
}
|
|
223
|
-
else if (arg ===
|
|
302
|
+
else if (arg === "--profile") {
|
|
224
303
|
if (i + 1 < args.length) {
|
|
225
304
|
options.profile = args[i + 1];
|
|
226
305
|
}
|
|
227
306
|
}
|
|
228
|
-
else if (arg ===
|
|
307
|
+
else if (arg === "--host" || arg === "-h") {
|
|
229
308
|
if (i + 1 < args.length) {
|
|
230
309
|
options.host = args[i + 1];
|
|
231
310
|
}
|
|
232
311
|
}
|
|
233
|
-
else if (arg ===
|
|
312
|
+
else if (arg === "--port" || arg === "-p") {
|
|
234
313
|
if (i + 1 < args.length) {
|
|
235
314
|
options.port = parseInt(args[i + 1], 10);
|
|
236
315
|
}
|
|
237
316
|
}
|
|
238
|
-
else if (arg ===
|
|
317
|
+
else if (arg === "--timeout" || arg === "-t") {
|
|
239
318
|
if (i + 1 < args.length) {
|
|
240
319
|
options.timeout = parseInt(args[i + 1], 10);
|
|
241
320
|
}
|
|
242
321
|
}
|
|
243
|
-
else if (arg ===
|
|
322
|
+
else if (arg === "--format" || arg === "-f") {
|
|
244
323
|
if (i + 1 < args.length) {
|
|
245
324
|
options.outputFormat = args[i + 1];
|
|
246
325
|
}
|
|
247
326
|
}
|
|
248
|
-
else if (arg ===
|
|
327
|
+
else if (arg === "--verbose" || arg === "-v") {
|
|
249
328
|
options.verbose = true;
|
|
250
329
|
}
|
|
251
|
-
else if (arg ===
|
|
330
|
+
else if (arg === "--quiet" || arg === "-q") {
|
|
252
331
|
options.quiet = true;
|
|
253
332
|
}
|
|
254
|
-
else if (arg ===
|
|
333
|
+
else if (arg === "--debug" || arg === "-d") {
|
|
255
334
|
options.debug = true;
|
|
256
335
|
}
|
|
257
|
-
else if (arg ===
|
|
336
|
+
else if (arg === "--target-index") {
|
|
258
337
|
if (i + 1 < args.length) {
|
|
259
338
|
options.targetIndex = parseInt(args[i + 1], 10);
|
|
260
339
|
}
|
|
@@ -271,15 +350,9 @@ class CLIApplication {
|
|
|
271
350
|
await this.client.disconnect();
|
|
272
351
|
}
|
|
273
352
|
catch (error) {
|
|
274
|
-
this.logger.error(
|
|
353
|
+
this.logger.error("Error during shutdown:", error);
|
|
275
354
|
}
|
|
276
355
|
}
|
|
277
|
-
try {
|
|
278
|
-
await this.proxyManager.shutdown();
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
this.logger.error('Error shutting down proxy manager:', error);
|
|
282
|
-
}
|
|
283
356
|
}
|
|
284
357
|
getCLI() {
|
|
285
358
|
return this.cli;
|