chrome-cdp-cli 2.0.4 → 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.
@@ -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 === 'function') {
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 === 'VERSION_COMMAND_EXECUTED') {
56
+ if (parseError.message === "VERSION_COMMAND_EXECUTED") {
60
57
  return CommandRouter_1.ExitCode.SUCCESS;
61
58
  }
62
- if (parseError.message === 'HELP_COMMAND_EXECUTED') {
59
+ if (parseError.message === "HELP_COMMAND_EXECUTED") {
63
60
  return CommandRouter_1.ExitCode.SUCCESS;
64
61
  }
65
62
  }
@@ -85,49 +82,148 @@ class CLIApplication {
85
82
  this.logger.setLevel(2);
86
83
  }
87
84
  this.configureDebugMode(command.config.debug);
88
- this.logger.debug('CLIApplication.run called with argv:', argv);
89
- this.logger.debug('Parsed command:', command);
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('Command needs connection, ensuring connection...');
97
- await this.ensureConnection(command);
88
+ this.logger.debug("Command needs connection, ensuring connection...");
89
+ try {
90
+ await this.ensureConnection(command);
91
+ }
92
+ catch (connectionError) {
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;
105
+ }
98
106
  }
99
- this.logger.debug('Executing command via CLI interface...');
107
+ this.logger.debug("Executing command via CLI interface...");
100
108
  const result = await this.cli.execute(command);
101
- this.logger.debug('Command execution result:', result);
109
+ this.logger.debug("Command execution result:", result);
102
110
  this.outputResult(result, command);
103
- return result.exitCode || (result.success ? CommandRouter_1.ExitCode.SUCCESS : CommandRouter_1.ExitCode.GENERAL_ERROR);
111
+ return (result.exitCode ||
112
+ (result.success ? CommandRouter_1.ExitCode.SUCCESS : CommandRouter_1.ExitCode.GENERAL_ERROR));
104
113
  }
105
114
  catch (error) {
106
- this.logger.debug('Error in CLIApplication.run:', error);
115
+ this.logger.debug("Error in CLIApplication.run:", error);
107
116
  const errorMessage = error instanceof Error ? error.message : String(error);
108
- console.error(this.formatError(errorMessage, { outputFormat: 'text', verbose: false, quiet: false, debug: false, host: 'localhost', port: 9222, timeout: 30000 }));
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
+ }));
109
126
  return CommandRouter_1.ExitCode.GENERAL_ERROR;
110
127
  }
111
128
  }
112
- async ensureProxyReady() {
113
- try {
114
- const isReady = await this.proxyManager.ensureProxyReady();
115
- if (!isReady) {
116
- }
117
- }
118
- catch (error) {
119
- }
120
- }
121
129
  needsConnection(commandName) {
122
130
  const noConnectionCommands = [
123
- 'help',
124
- 'connect',
125
- 'disconnect',
126
- 'install_cursor_command',
127
- 'install_claude_skill'
131
+ "help",
132
+ "connect",
133
+ "disconnect",
134
+ "install_cursor_command",
135
+ "install_claude_skill",
128
136
  ];
129
137
  return !noConnectionCommands.includes(commandName);
130
138
  }
139
+ isDevToolsWindow(target) {
140
+ const url = target.url.toLowerCase();
141
+ const title = target.title.toLowerCase();
142
+ return (url.startsWith("chrome-devtools://") ||
143
+ url.startsWith("devtools://") ||
144
+ title.includes("devtools") ||
145
+ title.includes("chrome devtools"));
146
+ }
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);
225
+ });
226
+ }
131
227
  async ensureConnection(command) {
132
228
  if (this.client) {
133
229
  return;
@@ -136,26 +232,55 @@ class CLIApplication {
136
232
  const targets = await this.connectionManager.discoverTargets(command.config.host, command.config.port);
137
233
  if (targets.length === 0) {
138
234
  throw new Error(`No Chrome targets found at ${command.config.host}:${command.config.port}. ` +
139
- 'Make sure Chrome is running with --remote-debugging-port=9222');
235
+ "Make sure Chrome is running with --remote-debugging-port=9222");
236
+ }
237
+ const pageTargets = targets.filter((target) => target.type === "page");
238
+ const nonDevToolsTargets = pageTargets.filter((target) => !this.isDevToolsWindow(target));
239
+ if (nonDevToolsTargets.length === 0) {
240
+ throw new Error("No page targets available (excluding DevTools windows). Open a tab in Chrome.");
241
+ }
242
+ let selectedTarget;
243
+ if (command.config.targetIndex !== undefined) {
244
+ const index = command.config.targetIndex - 1;
245
+ if (index < 0 || index >= nonDevToolsTargets.length) {
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}.`);
252
+ }
253
+ selectedTarget = nonDevToolsTargets[index];
254
+ }
255
+ else if (nonDevToolsTargets.length === 1) {
256
+ selectedTarget = nonDevToolsTargets[0];
140
257
  }
141
- const pageTarget = targets.find(target => target.type === 'page');
142
- if (!pageTarget) {
143
- throw new Error('No page targets available. Open a tab in Chrome.');
258
+ else {
259
+ selectedTarget = await this.selectTargetInteractive(nonDevToolsTargets);
144
260
  }
145
- this.client = await this.connectionManager.connectToTarget(pageTarget);
261
+ this.client = (await this.connectionManager.connectToTarget(selectedTarget));
146
262
  this.cli.setClient(this.client);
147
263
  if (command.config.verbose) {
148
- this.logger.info(`Connected to Chrome target: ${pageTarget.title} (${pageTarget.url})`);
264
+ this.logger.info(`Connected to Chrome target: ${selectedTarget.title} (${selectedTarget.url})`);
149
265
  }
150
266
  }
151
267
  catch (error) {
152
- throw new Error(`Failed to connect to Chrome: ${error instanceof Error ? error.message : String(error)}`);
268
+ if (error instanceof Error) {
269
+ throw error;
270
+ }
271
+ throw new Error(String(error));
153
272
  }
154
273
  }
155
274
  outputResult(result, command) {
275
+ if (result._rawOutput === true) {
276
+ return;
277
+ }
156
278
  if (command.config.quiet && result.success) {
157
279
  return;
158
280
  }
281
+ if (result.isLongRunning === true) {
282
+ return;
283
+ }
159
284
  const output = this.cli.formatOutput(result, command.config.outputFormat);
160
285
  if (result.success) {
161
286
  console.log(output);
@@ -169,45 +294,50 @@ class CLIApplication {
169
294
  const args = argv.slice(2);
170
295
  for (let i = 0; i < args.length; i++) {
171
296
  const arg = args[i];
172
- if (arg === '--config' || arg === '-c') {
297
+ if (arg === "--config" || arg === "-c") {
173
298
  if (i + 1 < args.length) {
174
299
  options.configFile = args[i + 1];
175
300
  }
176
301
  }
177
- else if (arg === '--profile') {
302
+ else if (arg === "--profile") {
178
303
  if (i + 1 < args.length) {
179
304
  options.profile = args[i + 1];
180
305
  }
181
306
  }
182
- else if (arg === '--host' || arg === '-h') {
307
+ else if (arg === "--host" || arg === "-h") {
183
308
  if (i + 1 < args.length) {
184
309
  options.host = args[i + 1];
185
310
  }
186
311
  }
187
- else if (arg === '--port' || arg === '-p') {
312
+ else if (arg === "--port" || arg === "-p") {
188
313
  if (i + 1 < args.length) {
189
314
  options.port = parseInt(args[i + 1], 10);
190
315
  }
191
316
  }
192
- else if (arg === '--timeout' || arg === '-t') {
317
+ else if (arg === "--timeout" || arg === "-t") {
193
318
  if (i + 1 < args.length) {
194
319
  options.timeout = parseInt(args[i + 1], 10);
195
320
  }
196
321
  }
197
- else if (arg === '--format' || arg === '-f') {
322
+ else if (arg === "--format" || arg === "-f") {
198
323
  if (i + 1 < args.length) {
199
324
  options.outputFormat = args[i + 1];
200
325
  }
201
326
  }
202
- else if (arg === '--verbose' || arg === '-v') {
327
+ else if (arg === "--verbose" || arg === "-v") {
203
328
  options.verbose = true;
204
329
  }
205
- else if (arg === '--quiet' || arg === '-q') {
330
+ else if (arg === "--quiet" || arg === "-q") {
206
331
  options.quiet = true;
207
332
  }
208
- else if (arg === '--debug' || arg === '-d') {
333
+ else if (arg === "--debug" || arg === "-d") {
209
334
  options.debug = true;
210
335
  }
336
+ else if (arg === "--target-index") {
337
+ if (i + 1 < args.length) {
338
+ options.targetIndex = parseInt(args[i + 1], 10);
339
+ }
340
+ }
211
341
  }
212
342
  return options;
213
343
  }
@@ -220,15 +350,9 @@ class CLIApplication {
220
350
  await this.client.disconnect();
221
351
  }
222
352
  catch (error) {
223
- this.logger.error('Error during shutdown:', error);
353
+ this.logger.error("Error during shutdown:", error);
224
354
  }
225
355
  }
226
- try {
227
- await this.proxyManager.shutdown();
228
- }
229
- catch (error) {
230
- this.logger.error('Error shutting down proxy manager:', error);
231
- }
232
356
  }
233
357
  getCLI() {
234
358
  return this.cli;