browser-cdp 0.6.1 → 0.6.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.
package/README.md CHANGED
@@ -37,6 +37,23 @@ browser-cdp pick '<message>'
37
37
  # Stream browser console output (network errors, exceptions, logs)
38
38
  browser-cdp console [--duration=SECONDS]
39
39
 
40
+ # Stream network requests/responses
41
+ browser-cdp network [--filter=PATTERN] [--json] [--errors] [--duration=SECONDS]
42
+
43
+ # Manage cookies (export/import/clear)
44
+ browser-cdp cookies export [--path=FILE]
45
+ browser-cdp cookies import <file>
46
+ browser-cdp cookies clear
47
+
48
+ # Manage localStorage/sessionStorage
49
+ browser-cdp storage get <key>
50
+ browser-cdp storage set <key> <value>
51
+ browser-cdp storage list
52
+ browser-cdp storage clear
53
+ browser-cdp storage export [--path=FILE]
54
+ browser-cdp storage import <file>
55
+ # Add --session for sessionStorage instead of localStorage
56
+
40
57
  # Show page performance metrics
41
58
  browser-cdp insights [--json]
42
59
 
@@ -89,6 +106,46 @@ browser-cdp console
89
106
  # Stream console for 10 seconds
90
107
  browser-cdp console --duration=10
91
108
 
109
+ # Stream network traffic
110
+ browser-cdp network
111
+ # Then navigate to see requests
112
+
113
+ # Filter to API calls only
114
+ browser-cdp network --filter=api
115
+
116
+ # Only show failed requests (4xx/5xx)
117
+ browser-cdp network --errors
118
+
119
+ # JSON output for parsing
120
+ browser-cdp network --json --duration=5 | jq '.url'
121
+
122
+ # Export cookies to JSON file
123
+ browser-cdp cookies export
124
+ # Returns: cookies.json with all cookies
125
+
126
+ # Export to custom file
127
+ browser-cdp cookies export --path session.json
128
+
129
+ # Import cookies from file
130
+ browser-cdp cookies import session.json
131
+
132
+ # Clear all cookies
133
+ browser-cdp cookies clear
134
+
135
+ # Get/set localStorage values
136
+ browser-cdp storage get authToken
137
+ browser-cdp storage set theme dark
138
+
139
+ # List all storage keys
140
+ browser-cdp storage list
141
+
142
+ # Export/import storage
143
+ browser-cdp storage export
144
+ browser-cdp storage import storage.json
145
+
146
+ # Work with sessionStorage
147
+ browser-cdp storage get tempData --session
148
+
92
149
  # Get page performance insights
93
150
  browser-cdp insights
94
151
  # Returns: TTFB, First Paint, FCP, DOM loaded, resources, memory
@@ -103,6 +160,107 @@ browser-cdp lighthouse
103
160
  browser-cdp close
104
161
  ```
105
162
 
163
+ ## Cookies Command
164
+
165
+ The `cookies` command provides session persistence for authenticated workflows:
166
+
167
+ ### Export Cookies
168
+
169
+ Save your browser cookies to a JSON file for later use:
170
+
171
+ ```bash
172
+ browser-cdp cookies export # Saves to cookies.json
173
+ browser-cdp cookies export --path auth.json # Save to specific file
174
+ ```
175
+
176
+ Output format:
177
+ ```json
178
+ [
179
+ {
180
+ "name": "session_id",
181
+ "value": "abc123...",
182
+ "domain": "example.com",
183
+ "path": "/",
184
+ "httpOnly": true,
185
+ "secure": true,
186
+ "sameSite": "Strict",
187
+ "expires": 1735689600
188
+ }
189
+ ]
190
+ ```
191
+
192
+ ### Import Cookies
193
+
194
+ Load previously exported cookies into the browser:
195
+
196
+ ```bash
197
+ browser-cdp cookies import auth.json
198
+ ```
199
+
200
+ Useful for:
201
+ - Resuming authenticated sessions across browser restarts
202
+ - Sharing sessions across team members
203
+ - Preserving login state for automation workflows
204
+
205
+ ### Clear Cookies
206
+
207
+ Delete all cookies from the browser:
208
+
209
+ ```bash
210
+ browser-cdp cookies clear
211
+ ```
212
+
213
+ ## Storage Command
214
+
215
+ The `storage` command provides localStorage and sessionStorage management:
216
+
217
+ ### Get/Set Values
218
+
219
+ ```bash
220
+ browser-cdp storage get authToken # Get from localStorage
221
+ browser-cdp storage set theme dark # Set in localStorage
222
+ browser-cdp storage set tempData "session" --session # Set in sessionStorage
223
+ ```
224
+
225
+ ### List Keys
226
+
227
+ ```bash
228
+ browser-cdp storage list # List localStorage keys
229
+ browser-cdp storage list --session # List sessionStorage keys
230
+ ```
231
+
232
+ ### Export/Import
233
+
234
+ Save storage to a JSON file for backup or restore:
235
+
236
+ ```bash
237
+ browser-cdp storage export # Saves to storage.json
238
+ browser-cdp storage export --path app-state.json # Save to specific file
239
+ browser-cdp storage import app-state.json # Restore from file
240
+ ```
241
+
242
+ Output format:
243
+ ```json
244
+ {
245
+ "authToken": "eyJhbGciOiJIUzI1NiIs...",
246
+ "theme": "dark"
247
+ }
248
+ ```
249
+
250
+ ### Clear Storage
251
+
252
+ ```bash
253
+ browser-cdp storage clear # Clear localStorage
254
+ browser-cdp storage clear --session # Clear sessionStorage
255
+ ```
256
+
257
+ ### localStorage vs sessionStorage
258
+
259
+ | Storage Type | Lifetime | Scope | Flag |
260
+ |--------------|----------|-------|------|
261
+ | localStorage | Permanent | Per origin | (default) |
262
+ | sessionStorage | Tab session | Per tab | `--session` |
263
+
106
264
  ## Pre-started Browser
107
265
 
108
266
  If you already have a browser running with CDP enabled, the CLI will connect to it:
package/cli.js CHANGED
@@ -20,6 +20,9 @@ const commands = {
20
20
  pdf: "./src/pdf.js",
21
21
  pick: "./src/pick.js",
22
22
  console: "./src/console.js",
23
+ network: "./src/network.js",
24
+ cookies: "./src/cookies.js",
25
+ storage: "./src/storage.js",
23
26
  insights: "./src/insights.js",
24
27
  lighthouse: "./src/lighthouse.js",
25
28
  };
@@ -39,6 +42,9 @@ function printUsage() {
39
42
  console.log(" pdf Export current page as PDF");
40
43
  console.log(" pick '<message>' Interactive element picker");
41
44
  console.log(" console Stream browser console output");
45
+ console.log(" network Stream network requests/responses");
46
+ console.log(" cookies Export/import/clear browser cookies");
47
+ console.log(" storage Manage localStorage/sessionStorage");
42
48
  console.log(" insights Show page performance metrics");
43
49
  console.log(" lighthouse Run Lighthouse audit");
44
50
  console.log("");
@@ -53,6 +59,8 @@ function printUsage() {
53
59
  console.log(" browser-cdp eval 'document.title'");
54
60
  console.log(" browser-cdp dom > page.html");
55
61
  console.log(" browser-cdp console --duration=10");
62
+ console.log(" browser-cdp network --filter=api");
63
+ console.log(" browser-cdp cookies export --path session.json");
56
64
  console.log(" browser-cdp insights --json");
57
65
  process.exit(0);
58
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-cdp",
3
- "version": "0.6.1",
3
+ "version": "0.6.4",
4
4
  "description": "Browser automation via Chrome DevTools Protocol - control Chrome, Brave, Edge with real browser profiles",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cookies.js ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync } from "node:fs";
4
+ import { chromium } from "playwright";
5
+ import { DEFAULT_PORT, getActivePage } from "./utils.js";
6
+
7
+ const args = process.argv.slice(2);
8
+ const subcommand = args[0];
9
+ const showHelp = args.includes("--help") || args.includes("-h");
10
+
11
+ function printUsage() {
12
+ console.log("Usage: cookies <subcommand> [options]");
13
+ console.log("");
14
+ console.log("Subcommands:");
15
+ console.log(" export Export cookies to JSON file");
16
+ console.log(" import <file> Import cookies from JSON file");
17
+ console.log(" clear Clear all cookies from browser");
18
+ console.log("");
19
+ console.log("Options:");
20
+ console.log(" --path <file> Output file path for export (default: cookies.json)");
21
+ console.log("");
22
+ console.log("Examples:");
23
+ console.log(" cookies export");
24
+ console.log(" cookies export --path session.json");
25
+ console.log(" cookies import session.json");
26
+ console.log(" cookies clear");
27
+ process.exit(0);
28
+ }
29
+
30
+ if (!subcommand || showHelp) {
31
+ printUsage();
32
+ }
33
+
34
+ if (!["export", "import", "clear"].includes(subcommand)) {
35
+ console.error(`Unknown subcommand: ${subcommand}`);
36
+ console.log("Available: export, import, clear");
37
+ process.exit(1);
38
+ }
39
+
40
+ const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
41
+ const contexts = browser.contexts();
42
+ const context = contexts[0];
43
+
44
+ if (!context) {
45
+ console.error("No browser context found");
46
+ process.exit(1);
47
+ }
48
+
49
+ const pages = context.pages();
50
+ const page = getActivePage(pages);
51
+
52
+ if (!page) {
53
+ console.error("No active tab found");
54
+ process.exit(1);
55
+ }
56
+
57
+ const cdp = await page.context().newCDPSession(page);
58
+
59
+ if (subcommand === "export") {
60
+ const pathIdx = args.findIndex((a) => a === "--path");
61
+ const outputFile = pathIdx !== -1 ? args[pathIdx + 1] : "cookies.json";
62
+
63
+ const { cookies } = await cdp.send("Network.getCookies");
64
+ writeFileSync(outputFile, JSON.stringify(cookies, null, 2));
65
+ console.log(`Exported ${cookies.length} cookie(s) to ${outputFile}`);
66
+ } else if (subcommand === "import") {
67
+ const importFile = args[1];
68
+
69
+ if (!importFile) {
70
+ console.error("Error: import requires a file path");
71
+ process.exit(1);
72
+ }
73
+
74
+ const cookies = JSON.parse(readFileSync(importFile, "utf8"));
75
+
76
+ if (!Array.isArray(cookies)) {
77
+ console.error("Error: Cookie file must contain a JSON array");
78
+ process.exit(1);
79
+ }
80
+
81
+ await cdp.send("Network.setCookies", { cookies });
82
+ console.log(`Imported ${cookies.length} cookie(s) from ${importFile}`);
83
+ } else if (subcommand === "clear") {
84
+ await cdp.send("Network.clearBrowserCookies");
85
+ console.log("Cleared all cookies");
86
+ }
87
+
88
+ await browser.close();
package/src/network.js ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { chromium } from "playwright";
4
+ import { DEFAULT_PORT, getActivePage, formatTime, resetColor } from "./utils.js";
5
+
6
+ const args = process.argv.slice(2);
7
+ const filterPattern = args.find((a) => a.startsWith("--filter="))?.split("=")[1];
8
+ const jsonOutput = args.includes("--json");
9
+ const errorsOnly = args.includes("--errors");
10
+ const duration = args.find((a) => a.startsWith("--duration="));
11
+ const durationMs = duration ? parseInt(duration.split("=")[1]) * 1000 : null;
12
+ const showHelp = args.includes("--help") || args.includes("-h");
13
+
14
+ if (showHelp) {
15
+ console.log("Usage: network.js [options]");
16
+ console.log("\nStream network requests and responses in real-time.");
17
+ console.log("\nOptions:");
18
+ console.log(" --filter=PATTERN Filter URLs by regex pattern");
19
+ console.log(" --json Output as JSON for piping");
20
+ console.log(" --errors Only show failed requests (4xx/5xx)");
21
+ console.log(" --duration=N Stop after N seconds (default: run until Ctrl+C)");
22
+ console.log("\nExamples:");
23
+ console.log(" network.js # Stream all network traffic");
24
+ console.log(" network.js --filter=api # Only show URLs containing 'api'");
25
+ console.log(" network.js --errors # Only show failed requests");
26
+ console.log(" network.js --json # JSON output for jq/parsing");
27
+ console.log(" network.js --duration=10 # Capture for 10 seconds");
28
+ process.exit(0);
29
+ }
30
+
31
+ const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
32
+ const contexts = browser.contexts();
33
+ const context = contexts[0];
34
+
35
+ if (!context) {
36
+ console.error("No browser context found");
37
+ process.exit(1);
38
+ }
39
+
40
+ const pages = context.pages();
41
+ const page = getActivePage(pages);
42
+
43
+ if (!page) {
44
+ console.error("No active tab found");
45
+ process.exit(1);
46
+ }
47
+
48
+ console.error(`Connected to: ${page.url()}`);
49
+
50
+ const cdp = await page.context().newCDPSession(page);
51
+ await cdp.send("Network.enable");
52
+
53
+ const requests = new Map();
54
+
55
+ const statusColors = {
56
+ success: "\x1b[32m", // green for 2xx
57
+ redirect: "\x1b[33m", // yellow for 3xx
58
+ error: "\x1b[31m", // red for 4xx/5xx
59
+ request: "\x1b[36m", // cyan for requests
60
+ };
61
+
62
+ function matchesFilter(url) {
63
+ if (!filterPattern) return true;
64
+ try {
65
+ return new RegExp(filterPattern).test(url);
66
+ } catch {
67
+ return url.includes(filterPattern);
68
+ }
69
+ }
70
+
71
+ cdp.on("Network.requestWillBeSent", ({ requestId, request, timestamp }) => {
72
+ requests.set(requestId, { method: request.method, url: request.url, timestamp });
73
+
74
+ if (!matchesFilter(request.url)) return;
75
+ if (errorsOnly) return;
76
+
77
+ if (jsonOutput) {
78
+ console.log(
79
+ JSON.stringify({
80
+ type: "request",
81
+ timestamp: new Date(timestamp * 1000).toISOString(),
82
+ method: request.method,
83
+ url: request.url,
84
+ requestId,
85
+ })
86
+ );
87
+ } else {
88
+ console.log(`${statusColors.request}[${formatTime()}] → ${request.method} ${request.url}${resetColor}`);
89
+ }
90
+ });
91
+
92
+ cdp.on("Network.responseReceived", ({ requestId, response, timestamp, type }) => {
93
+ const req = requests.get(requestId);
94
+
95
+ if (!matchesFilter(response.url)) return;
96
+ if (errorsOnly && response.status < 400) return;
97
+
98
+ if (jsonOutput) {
99
+ console.log(
100
+ JSON.stringify({
101
+ type: "response",
102
+ timestamp: new Date(timestamp * 1000).toISOString(),
103
+ status: response.status,
104
+ statusText: response.statusText,
105
+ url: response.url,
106
+ mimeType: response.mimeType,
107
+ resourceType: type,
108
+ requestId,
109
+ method: req?.method,
110
+ })
111
+ );
112
+ } else {
113
+ let color = statusColors.success;
114
+ if (response.status >= 400) {
115
+ color = statusColors.error;
116
+ } else if (response.status >= 300) {
117
+ color = statusColors.redirect;
118
+ }
119
+
120
+ const method = req ? `${req.method} ` : "";
121
+ console.log(`${color}[${formatTime()}] ← ${response.status} ${method}${response.url}${resetColor}`);
122
+ }
123
+ });
124
+
125
+ cdp.on("Network.loadingFailed", ({ requestId, errorText, blockedReason, canceled }) => {
126
+ const req = requests.get(requestId);
127
+
128
+ if (req && !matchesFilter(req.url)) return;
129
+
130
+ if (jsonOutput) {
131
+ console.log(
132
+ JSON.stringify({
133
+ type: "failed",
134
+ timestamp: new Date().toISOString(),
135
+ url: req?.url,
136
+ method: req?.method,
137
+ error: errorText,
138
+ blockedReason,
139
+ canceled,
140
+ requestId,
141
+ })
142
+ );
143
+ } else {
144
+ const reason = blockedReason ? ` (${blockedReason})` : "";
145
+ const canceledStr = canceled ? " [CANCELED]" : "";
146
+ console.log(
147
+ `${statusColors.error}[${formatTime()}] ✗ ${errorText}${reason}${canceledStr} - ${req?.url || "unknown"}${resetColor}`
148
+ );
149
+ }
150
+
151
+ if (requestId) {
152
+ requests.delete(requestId);
153
+ }
154
+ });
155
+
156
+ console.error(`Listening for network activity... (Ctrl+C to stop)`);
157
+
158
+ if (durationMs) {
159
+ await new Promise((r) => setTimeout(r, durationMs));
160
+ await browser.close();
161
+ } else {
162
+ // Keep running until interrupted
163
+ process.on("SIGINT", async () => {
164
+ console.error("\nStopping...");
165
+ await browser.close();
166
+ process.exit(0);
167
+ });
168
+
169
+ // Keep the process alive
170
+ await new Promise(() => {});
171
+ }
package/src/storage.js ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync } from "node:fs";
4
+ import { chromium } from "playwright";
5
+ import { DEFAULT_PORT, getActivePage } from "./utils.js";
6
+
7
+ const args = process.argv.slice(2);
8
+ const subcommand = args[0];
9
+ const showHelp = args.includes("--help") || args.includes("-h");
10
+ const useSessionStorage = args.includes("--session");
11
+ const storageType = useSessionStorage ? "sessionStorage" : "localStorage";
12
+
13
+ function printUsage() {
14
+ console.log("Usage: storage <subcommand> [options]");
15
+ console.log("");
16
+ console.log("Subcommands:");
17
+ console.log(" get <key> Get value for key");
18
+ console.log(" set <key> <value> Set key to value");
19
+ console.log(" list List all keys");
20
+ console.log(" clear Clear all storage");
21
+ console.log(" export Export storage to JSON file");
22
+ console.log(" import <file> Import storage from JSON file");
23
+ console.log("");
24
+ console.log("Options:");
25
+ console.log(" --session Use sessionStorage instead of localStorage");
26
+ console.log(" --path <file> Output file path for export (default: storage.json)");
27
+ console.log("");
28
+ console.log("Examples:");
29
+ console.log(" storage get token");
30
+ console.log(" storage set theme dark");
31
+ console.log(" storage list");
32
+ console.log(" storage export");
33
+ console.log(" storage export --path session.json --session");
34
+ console.log(" storage import session.json");
35
+ console.log(" storage clear");
36
+ process.exit(0);
37
+ }
38
+
39
+ if (!subcommand || showHelp) {
40
+ printUsage();
41
+ }
42
+
43
+ if (!["get", "set", "list", "clear", "export", "import"].includes(subcommand)) {
44
+ console.error(`Unknown subcommand: ${subcommand}`);
45
+ console.log("Available: get, set, list, clear, export, import");
46
+ process.exit(1);
47
+ }
48
+
49
+ const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
50
+ const contexts = browser.contexts();
51
+ const context = contexts[0];
52
+
53
+ if (!context) {
54
+ console.error("No browser context found");
55
+ process.exit(1);
56
+ }
57
+
58
+ const pages = context.pages();
59
+ const page = getActivePage(pages);
60
+
61
+ if (!page) {
62
+ console.error("No active tab found");
63
+ process.exit(1);
64
+ }
65
+
66
+ const positionalArgs = args.filter((a) => !a.startsWith("--"));
67
+
68
+ if (subcommand === "get") {
69
+ const key = positionalArgs[1];
70
+
71
+ if (!key) {
72
+ console.error("Error: get requires a key");
73
+ process.exit(1);
74
+ }
75
+
76
+ const result = await page.evaluate(
77
+ ([storageType, key]) => window[storageType].getItem(key),
78
+ [storageType, key]
79
+ );
80
+
81
+ if (result === null) {
82
+ console.error(`Key "${key}" not found in ${storageType}`);
83
+ process.exit(1);
84
+ }
85
+
86
+ console.log(result);
87
+ } else if (subcommand === "set") {
88
+ const key = positionalArgs[1];
89
+ const value = positionalArgs.slice(2).join(" ");
90
+
91
+ if (!key || value === "") {
92
+ console.error("Error: set requires <key> <value>");
93
+ process.exit(1);
94
+ }
95
+
96
+ await page.evaluate(
97
+ ([storageType, key, value]) => window[storageType].setItem(key, value),
98
+ [storageType, key, value]
99
+ );
100
+
101
+ console.log(`Set ${key} in ${storageType}`);
102
+ } else if (subcommand === "list") {
103
+ const keys = await page.evaluate(
104
+ (storageType) => Object.keys(window[storageType]),
105
+ storageType
106
+ );
107
+
108
+ if (keys.length === 0) {
109
+ console.log(`No keys found in ${storageType}`);
110
+ } else {
111
+ console.log(`Keys in ${storageType}:`);
112
+ keys.forEach((key) => console.log(` ${key}`));
113
+ }
114
+ } else if (subcommand === "clear") {
115
+ await page.evaluate((storageType) => window[storageType].clear(), storageType);
116
+
117
+ console.log(`Cleared all ${storageType}`);
118
+ } else if (subcommand === "export") {
119
+ const pathIdx = args.findIndex((a) => a === "--path");
120
+ const outputFile = pathIdx !== -1 ? args[pathIdx + 1] : "storage.json";
121
+
122
+ const data = await page.evaluate((storageType) => {
123
+ const storage = window[storageType];
124
+ const result = {};
125
+ for (let i = 0; i < storage.length; i++) {
126
+ const key = storage.key(i);
127
+ result[key] = storage.getItem(key);
128
+ }
129
+ return result;
130
+ }, storageType);
131
+
132
+ writeFileSync(outputFile, JSON.stringify(data, null, 2));
133
+ const count = Object.keys(data).length;
134
+ console.log(`Exported ${count} item(s) from ${storageType} to ${outputFile}`);
135
+ } else if (subcommand === "import") {
136
+ const importFile = positionalArgs[1];
137
+
138
+ if (!importFile) {
139
+ console.error("Error: import requires a file path");
140
+ process.exit(1);
141
+ }
142
+
143
+ let data;
144
+ try {
145
+ data = JSON.parse(readFileSync(importFile, "utf8"));
146
+ } catch (error) {
147
+ console.error(`Error reading file: ${error.message}`);
148
+ process.exit(1);
149
+ }
150
+
151
+ if (typeof data !== "object" || Array.isArray(data)) {
152
+ console.error("Error: Storage file must contain a JSON object");
153
+ process.exit(1);
154
+ }
155
+
156
+ await page.evaluate(
157
+ ([storageType, data]) => {
158
+ const storage = window[storageType];
159
+ for (const [key, value] of Object.entries(data)) {
160
+ storage.setItem(key, value);
161
+ }
162
+ },
163
+ [storageType, data]
164
+ );
165
+
166
+ const count = Object.keys(data).length;
167
+ console.log(`Imported ${count} item(s) to ${storageType} from ${importFile}`);
168
+ }
169
+
170
+ await browser.close();