chrome-cdp-cli 2.1.2 → 2.1.4

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.
@@ -141,6 +141,9 @@ class CLIApplication {
141
141
  const title = target.title.toLowerCase();
142
142
  return (url.startsWith("chrome-devtools://") ||
143
143
  url.startsWith("devtools://") ||
144
+ url.startsWith("chrome://") ||
145
+ url.startsWith("chrome-extension://") ||
146
+ url.startsWith("chrome-untrusted://") ||
144
147
  title.includes("devtools") ||
145
148
  title.includes("chrome devtools"));
146
149
  }
@@ -161,23 +164,18 @@ class CLIApplication {
161
164
  const CYAN = "\x1b[36m";
162
165
  const DIM = "\x1b[2m";
163
166
  const CLEAR_LINE = "\x1b[2K\x1b[G";
164
- const SAVE = "\x1b[s";
165
- const RESTORE = "\x1b[u";
167
+ const TOTAL_LINES = targets.length * 2 + 5;
166
168
  const truncate = (s, max) => s.length > max ? s.substring(0, max - 1) + "…" : s;
167
169
  const render = (first) => {
168
- if (first) {
169
- process.stderr.write(SAVE);
170
- }
171
- else {
172
- process.stderr.write(RESTORE);
170
+ if (!first) {
171
+ process.stderr.write(`\x1b[${TOTAL_LINES}A`);
173
172
  }
174
173
  process.stderr.write(`${CLEAR_LINE}${BOLD}Select a Chrome page${RESET} (↑↓ navigate, Enter select, q quit)\n`);
175
174
  process.stderr.write(`${CLEAR_LINE}${"─".repeat(54)}\n`);
176
175
  targets.forEach((t, i) => {
177
176
  const num = `[${i + 1}]`;
178
- const rawTitle = t.title || "(Untitled)";
179
- const title = truncate(rawTitle, 52);
180
- const url = truncate(t.url, 60);
177
+ const title = truncate(t.title || "(Untitled)", 50);
178
+ const url = truncate(t.url, 58);
181
179
  const selected = i === cursor;
182
180
  if (selected) {
183
181
  process.stderr.write(`${CLEAR_LINE}${CYAN}${BOLD} ❯ ${num} ${title}${RESET}\n`);
@@ -189,8 +187,8 @@ class CLIApplication {
189
187
  }
190
188
  });
191
189
  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`);
190
+ process.stderr.write(`${CLEAR_LINE}${DIM}Tip: skip this prompt with cdp -i <number> <command>${RESET}\n`);
191
+ process.stderr.write(`${CLEAR_LINE}${DIM} or close other tabs until only one remains.${RESET}\n`);
194
192
  };
195
193
  render(true);
196
194
  process.stdin.setRawMode(true);
@@ -568,7 +568,7 @@ class CommandSchemaRegistry {
568
568
  name: "log",
569
569
  aliases: ["console"],
570
570
  description: "Follow console messages in real-time",
571
- usage: "cdp log [options]",
571
+ usage: "cdp log [PATTERN] [options]",
572
572
  examples: [
573
573
  { command: "cdp log", description: "Follow all console messages" },
574
574
  {
@@ -576,8 +576,20 @@ class CommandSchemaRegistry {
576
576
  description: "Follow only errors and warnings",
577
577
  },
578
578
  {
579
- command: 'cdp log --textPattern "API"',
580
- description: "Follow messages matching /API/i",
579
+ command: "cdp log '\\[AI'",
580
+ description: "Follow messages matching regex /\\[AI/i",
581
+ },
582
+ {
583
+ command: "cdp log -e error -e warn",
584
+ description: "Follow messages matching 'error' OR 'warn'",
585
+ },
586
+ {
587
+ command: "cdp log -F '[AI'",
588
+ description: "Follow messages containing literal string '[AI'",
589
+ },
590
+ {
591
+ command: "cdp log -v 'debug'",
592
+ description: "Follow messages NOT matching 'debug'",
581
593
  },
582
594
  {
583
595
  command: "cdp log --format json",
@@ -592,9 +604,28 @@ class CommandSchemaRegistry {
592
604
  type: "string",
593
605
  },
594
606
  {
595
- name: "textPattern",
596
- description: "Filter by text pattern (regex, case-insensitive)",
607
+ name: "expression",
608
+ short: "e",
609
+ description: "Pattern to match (regex by default). May be specified multiple times (OR logic).",
597
610
  type: "string",
611
+ multiple: true,
612
+ },
613
+ {
614
+ name: "fixed-strings",
615
+ short: "F",
616
+ description: "Treat pattern(s) as literal strings, not regexps",
617
+ type: "boolean",
618
+ },
619
+ {
620
+ name: "invert-match",
621
+ short: "v",
622
+ description: "Output messages that do NOT match the pattern",
623
+ type: "boolean",
624
+ },
625
+ {
626
+ name: "case-sensitive",
627
+ description: "Match is case-sensitive (default: case-insensitive)",
628
+ type: "boolean",
598
629
  },
599
630
  {
600
631
  name: "follow",
@@ -609,7 +640,14 @@ class CommandSchemaRegistry {
609
640
  choices: ["text", "json", "pretty"],
610
641
  },
611
642
  ],
612
- arguments: [],
643
+ arguments: [
644
+ {
645
+ name: "pattern",
646
+ description: "Pattern to match (regex by default, case-insensitive)",
647
+ type: "string",
648
+ required: false,
649
+ },
650
+ ],
613
651
  });
614
652
  this.registerCommand({
615
653
  name: "network",
@@ -20,21 +20,44 @@ class ListConsoleMessagesHandler {
20
20
  };
21
21
  }
22
22
  }
23
+ buildMatcher(params) {
24
+ const rawExpressions = params.expression
25
+ ? Array.isArray(params.expression)
26
+ ? params.expression
27
+ : [params.expression]
28
+ : [];
29
+ const patterns = [
30
+ ...(params.pattern ? [params.pattern] : []),
31
+ ...rawExpressions,
32
+ ];
33
+ if (patterns.length === 0)
34
+ return () => true;
35
+ const fixedStrings = params.fixedStrings ?? params["fixed-strings"] ?? false;
36
+ const caseSensitive = params.caseSensitive ?? params["case-sensitive"] ?? false;
37
+ const invertMatch = params.invertMatch ?? params["invert-match"] ?? false;
38
+ const flags = caseSensitive ? "" : "i";
39
+ const tests = patterns.map((p) => {
40
+ if (fixedStrings) {
41
+ return caseSensitive
42
+ ? (text) => text.includes(p)
43
+ : (text) => text.toLowerCase().includes(p.toLowerCase());
44
+ }
45
+ const re = new RegExp(p, flags);
46
+ return (text) => re.test(text);
47
+ });
48
+ const anyMatch = (text) => tests.some((t) => t(text));
49
+ return invertMatch ? (text) => !anyMatch(text) : anyMatch;
50
+ }
23
51
  async executeFollowMode(client, params) {
24
52
  if (!this.consoleMonitor) {
25
53
  this.consoleMonitor = new ConsoleMonitor_1.ConsoleMonitor(client);
26
54
  }
27
55
  await this.consoleMonitor.startMonitoring();
28
- const filter = {};
29
- if (params.types && params.types.length > 0) {
30
- filter.types = params.types;
31
- }
32
- if (params.textPattern) {
33
- filter.textPattern = params.textPattern;
34
- }
56
+ const typeFilter = params.types && params.types.length > 0 ? params.types : null;
57
+ const matchText = this.buildMatcher(params);
35
58
  const outputFormat = params.format || "text";
36
59
  const messageCallback = (message) => {
37
- if (!this.shouldOutputMessage(message, filter)) {
60
+ if (!this.shouldOutputMessage(message, typeFilter, matchText)) {
38
61
  return;
39
62
  }
40
63
  this.outputMessage(message, outputFormat);
@@ -69,19 +92,11 @@ class ListConsoleMessagesHandler {
69
92
  isLongRunning: true,
70
93
  };
71
94
  }
72
- shouldOutputMessage(message, filter) {
73
- if (filter.types && filter.types.length > 0) {
74
- if (!filter.types.includes(message.type)) {
75
- return false;
76
- }
77
- }
78
- if (filter.textPattern) {
79
- const pattern = new RegExp(filter.textPattern, "i");
80
- if (!pattern.test(message.text)) {
81
- return false;
82
- }
95
+ shouldOutputMessage(message, typeFilter, matchText) {
96
+ if (typeFilter && !typeFilter.includes(message.type)) {
97
+ return false;
83
98
  }
84
- return true;
99
+ return matchText(message.text);
85
100
  }
86
101
  outputMessage(message, format) {
87
102
  switch (format) {
@@ -142,10 +157,16 @@ class ListConsoleMessagesHandler {
142
157
  return false;
143
158
  }
144
159
  }
145
- if (params.textPattern !== undefined &&
146
- typeof params.textPattern !== "string") {
160
+ if (params.pattern !== undefined && typeof params.pattern !== "string") {
147
161
  return false;
148
162
  }
163
+ if (params.expression !== undefined) {
164
+ const exprs = Array.isArray(params.expression)
165
+ ? params.expression
166
+ : [params.expression];
167
+ if (!exprs.every((e) => typeof e === "string"))
168
+ return false;
169
+ }
149
170
  if (params.format !== undefined) {
150
171
  const validFormats = ["text", "json", "pretty"];
151
172
  if (typeof params.format !== "string" ||
@@ -159,21 +180,30 @@ class ListConsoleMessagesHandler {
159
180
  return `log - Follow console messages in real-time
160
181
 
161
182
  Usage:
162
- cdp log [options]
183
+ cdp log [PATTERN] [options]
184
+
185
+ Arguments:
186
+ PATTERN Pattern to match (regex, case-insensitive by default)
163
187
 
164
188
  Options:
165
- --types <types> Filter by message types (comma-separated: log,info,warn,error,debug)
166
- --textPattern <pattern> Filter by text pattern (regex, case-insensitive)
167
- --format <format> Output format: text, json, or pretty (default: text)
189
+ -e, --expression <pat> Pattern to match; may be used multiple times (OR logic)
190
+ -F, --fixed-strings Treat pattern(s) as literal strings, not regexps
191
+ -v, --invert-match Output messages that do NOT match the pattern
192
+ --case-sensitive Case-sensitive matching (default: case-insensitive)
193
+ --types <types> Filter by message types (log,info,warn,error,debug)
194
+ --format <format> Output format: text, json, or pretty (default: text)
168
195
  -f, --follow Alias flag (follow mode is always active)
169
196
 
170
197
  Examples:
171
- cdp log # Follow all console messages
172
- cdp log --types error,warn # Follow only errors and warnings
173
- cdp log --textPattern "API" # Follow messages matching /API/i
174
- cdp log --format json # Output as JSON (one object per line)
175
- cdp log --format pretty # Colorized output
176
- cdp log --types error --textPattern "404" # Combined filters
198
+ cdp log # Follow all console messages
199
+ cdp log '\\[AI' # Messages matching regex /\\[AI/i
200
+ cdp log -e error -e warn # Messages containing 'error' OR 'warn'
201
+ cdp log -F '[AI' # Messages containing literal '[AI'
202
+ cdp log -v debug # Messages NOT containing 'debug'
203
+ cdp log '404' --case-sensitive # Case-sensitive match
204
+ cdp log --types error,warn # Filter by type
205
+ cdp log --format json # Output as JSON (one object per line)
206
+ cdp log --format pretty # Colorized output
177
207
 
178
208
  Note:
179
209
  This command runs continuously and streams console messages in real-time.
@@ -53,7 +53,7 @@ class Logger {
53
53
  maxFiles: 5,
54
54
  enableConsole: true,
55
55
  enableStructured: false,
56
- ...config
56
+ ...config,
57
57
  };
58
58
  if (this.config.file) {
59
59
  this.initializeLogFile();
@@ -74,13 +74,15 @@ class Logger {
74
74
  this.rotateLogFile();
75
75
  }
76
76
  }
77
- this.logFileHandle = fs.createWriteStream(this.config.file, { flags: 'a' });
78
- this.logFileHandle.on('error', (error) => {
79
- console.error('Log file write error:', error);
77
+ this.logFileHandle = fs.createWriteStream(this.config.file, {
78
+ flags: "a",
79
+ });
80
+ this.logFileHandle.on("error", (error) => {
81
+ console.error("Log file write error:", error);
80
82
  });
81
83
  }
82
84
  catch (error) {
83
- console.error('Failed to initialize log file:', error);
85
+ console.error("Failed to initialize log file:", error);
84
86
  }
85
87
  }
86
88
  rotateLogFile() {
@@ -104,25 +106,29 @@ class Logger {
104
106
  fs.renameSync(this.config.file, `${this.config.file}.1`);
105
107
  }
106
108
  this.currentFileSize = 0;
107
- this.logFileHandle = fs.createWriteStream(this.config.file, { flags: 'a' });
108
- this.logFileHandle.on('error', (error) => {
109
- console.error('Log file write error:', error);
109
+ this.logFileHandle = fs.createWriteStream(this.config.file, {
110
+ flags: "a",
111
+ });
112
+ this.logFileHandle.on("error", (error) => {
113
+ console.error("Log file write error:", error);
110
114
  });
111
115
  }
112
116
  catch (error) {
113
- console.error('Failed to rotate log file:', error);
117
+ console.error("Failed to rotate log file:", error);
114
118
  }
115
119
  }
116
120
  formatLogEntry(entry) {
117
121
  if (this.config.enableStructured) {
118
- return JSON.stringify(entry) + '\n';
122
+ return JSON.stringify(entry) + "\n";
119
123
  }
120
124
  else {
121
125
  const timestamp = entry.timestamp;
122
126
  const level = entry.level.padEnd(5);
123
- const component = entry.component ? `[${entry.component}]` : '';
124
- const connectionId = entry.connectionId ? `{conn:${entry.connectionId}}` : '';
125
- const clientId = entry.clientId ? `{client:${entry.clientId}}` : '';
127
+ const component = entry.component ? `[${entry.component}]` : "";
128
+ const connectionId = entry.connectionId
129
+ ? `{conn:${entry.connectionId}}`
130
+ : "";
131
+ const clientId = entry.clientId ? `{client:${entry.clientId}}` : "";
126
132
  let message = `${timestamp} ${level} ${component}${connectionId}${clientId} ${entry.message}`;
127
133
  if (entry.data) {
128
134
  message += ` | Data: ${JSON.stringify(entry.data)}`;
@@ -136,7 +142,7 @@ class Logger {
136
142
  if (entry.metrics) {
137
143
  message += ` | Metrics: ${JSON.stringify(entry.metrics)}`;
138
144
  }
139
- return message + '\n';
145
+ return message + "\n";
140
146
  }
141
147
  }
142
148
  writeLog(entry) {
@@ -145,24 +151,12 @@ class Logger {
145
151
  const consoleMessage = this.config.enableStructured
146
152
  ? formattedEntry.trim()
147
153
  : formattedEntry.trim();
148
- switch (entry.level) {
149
- case 'ERROR':
150
- console.error(consoleMessage);
151
- break;
152
- case 'WARN':
153
- console.warn(consoleMessage);
154
- break;
155
- case 'INFO':
156
- console.info(consoleMessage);
157
- break;
158
- case 'DEBUG':
159
- console.debug(consoleMessage);
160
- break;
161
- }
154
+ process.stderr.write(consoleMessage + "\n");
162
155
  }
163
156
  if (this.logFileHandle) {
164
- const entrySize = Buffer.byteLength(formattedEntry, 'utf8');
165
- if (this.currentFileSize + entrySize >= (this.config.maxFileSize || 10 * 1024 * 1024)) {
157
+ const entrySize = Buffer.byteLength(formattedEntry, "utf8");
158
+ if (this.currentFileSize + entrySize >=
159
+ (this.config.maxFileSize || 10 * 1024 * 1024)) {
166
160
  this.rotateLogFile();
167
161
  }
168
162
  this.logFileHandle.write(formattedEntry);
@@ -174,7 +168,7 @@ class Logger {
174
168
  timestamp: new Date().toISOString(),
175
169
  level,
176
170
  message,
177
- component: context?.component || this.config.component
171
+ component: context?.component || this.config.component,
178
172
  };
179
173
  if (context?.connectionId)
180
174
  entry.connectionId = context.connectionId;
@@ -188,7 +182,7 @@ class Logger {
188
182
  entry.error = {
189
183
  message: error.message,
190
184
  stack: error.stack,
191
- code: error.code
185
+ code: error.code,
192
186
  };
193
187
  }
194
188
  return entry;
@@ -214,57 +208,59 @@ class Logger {
214
208
  data = errorOrData;
215
209
  error = undefined;
216
210
  }
217
- const entry = this.createLogEntry('ERROR', message, data, error, context);
211
+ const entry = this.createLogEntry("ERROR", message, data, error, context);
218
212
  this.writeLog(entry);
219
213
  }
220
214
  }
221
215
  warn(message, data, context) {
222
216
  if (this.config.level >= LogLevel.WARN) {
223
- const entry = this.createLogEntry('WARN', message, data, undefined, context);
217
+ const entry = this.createLogEntry("WARN", message, data, undefined, context);
224
218
  this.writeLog(entry);
225
219
  }
226
220
  }
227
221
  info(message, data, context) {
228
222
  if (this.config.level >= LogLevel.INFO) {
229
- const entry = this.createLogEntry('INFO', message, data, undefined, context);
223
+ const entry = this.createLogEntry("INFO", message, data, undefined, context);
230
224
  this.writeLog(entry);
231
225
  }
232
226
  }
233
227
  debug(message, data, context) {
234
228
  if (this.config.level >= LogLevel.DEBUG) {
235
- const entry = this.createLogEntry('DEBUG', message, data, undefined, context);
229
+ const entry = this.createLogEntry("DEBUG", message, data, undefined, context);
236
230
  this.writeLog(entry);
237
231
  }
238
232
  }
239
233
  logServerEvent(event, message, data, error) {
240
- const metrics = event === 'startup' || event === 'shutdown' ? {
241
- memoryUsage: process.memoryUsage()
242
- } : undefined;
243
- const entry = this.createLogEntry(event === 'error' ? 'ERROR' : 'INFO', `[SERVER-${event.toUpperCase()}] ${message}`, data, error, { component: 'ProxyServer', metrics });
234
+ const metrics = event === "startup" || event === "shutdown"
235
+ ? {
236
+ memoryUsage: process.memoryUsage(),
237
+ }
238
+ : undefined;
239
+ const entry = this.createLogEntry(event === "error" ? "ERROR" : "INFO", `[SERVER-${event.toUpperCase()}] ${message}`, data, error, { component: "ProxyServer", metrics });
244
240
  this.writeLog(entry);
245
241
  }
246
242
  logConnectionEvent(event, connectionId, message, data, error) {
247
- const entry = this.createLogEntry(error ? 'ERROR' : 'INFO', `[CONNECTION-${event.toUpperCase()}] ${message}`, data, error, { component: 'ConnectionPool', connectionId });
243
+ const entry = this.createLogEntry(error ? "ERROR" : "INFO", `[CONNECTION-${event.toUpperCase()}] ${message}`, data, error, { component: "ConnectionPool", connectionId });
248
244
  this.writeLog(entry);
249
245
  }
250
246
  logClientEvent(event, clientId, message, data, error) {
251
- const entry = this.createLogEntry(error ? 'ERROR' : 'INFO', `[CLIENT-${event.toUpperCase()}] ${message}`, data, error, { component: 'WSProxy', clientId });
247
+ const entry = this.createLogEntry(error ? "ERROR" : "INFO", `[CLIENT-${event.toUpperCase()}] ${message}`, data, error, { component: "WSProxy", clientId });
252
248
  this.writeLog(entry);
253
249
  }
254
250
  logMemoryEvent(event, message, metrics) {
255
- const entry = this.createLogEntry('INFO', `[MEMORY-${event.toUpperCase()}] ${message}`, undefined, undefined, { component: 'MemoryManager', metrics });
251
+ const entry = this.createLogEntry("INFO", `[MEMORY-${event.toUpperCase()}] ${message}`, undefined, undefined, { component: "MemoryManager", metrics });
256
252
  this.writeLog(entry);
257
253
  }
258
254
  logAPIEvent(method, path, statusCode, duration, clientIP, error) {
259
- const entry = this.createLogEntry(error ? 'ERROR' : 'INFO', `[API] ${method} ${path} - ${statusCode} (${duration}ms)`, { clientIP, duration, statusCode }, error, { component: 'ProxyAPI' });
255
+ const entry = this.createLogEntry(error ? "ERROR" : "INFO", `[API] ${method} ${path} - ${statusCode} (${duration}ms)`, { clientIP, duration, statusCode }, error, { component: "ProxyAPI" });
260
256
  this.writeLog(entry);
261
257
  }
262
258
  logSecurityEvent(event, message, data, error) {
263
- const entry = this.createLogEntry(error ? 'ERROR' : 'WARN', `[SECURITY-${event.toUpperCase()}] ${message}`, data, error, { component: 'SecurityManager' });
259
+ const entry = this.createLogEntry(error ? "ERROR" : "WARN", `[SECURITY-${event.toUpperCase()}] ${message}`, data, error, { component: "SecurityManager" });
264
260
  this.writeLog(entry);
265
261
  }
266
262
  logPerformanceMetrics(component, metrics) {
267
- const entry = this.createLogEntry('INFO', `[PERFORMANCE] ${component} metrics`, undefined, undefined, { component, metrics });
263
+ const entry = this.createLogEntry("INFO", `[PERFORMANCE] ${component} metrics`, undefined, undefined, { component, metrics });
268
264
  this.writeLog(entry);
269
265
  }
270
266
  close() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-cdp-cli",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "Browser automation CLI via Chrome DevTools Protocol. Designed for developers and AI assistants - combines dedicated commands for common tasks with flexible JavaScript execution for complex scenarios. Features: element interaction, screenshots, DOM snapshots, console/network monitoring. Built-in IDE integration for Cursor and Claude.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",