browser-debugger-cli 0.0.1-alpha.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.
Files changed (58) hide show
  1. package/README.md +85 -0
  2. package/dist/collectors/console.d.ts +17 -0
  3. package/dist/collectors/console.d.ts.map +1 -0
  4. package/dist/collectors/console.js +65 -0
  5. package/dist/collectors/console.js.map +1 -0
  6. package/dist/collectors/dom.d.ts +21 -0
  7. package/dist/collectors/dom.d.ts.map +1 -0
  8. package/dist/collectors/dom.js +88 -0
  9. package/dist/collectors/dom.js.map +1 -0
  10. package/dist/collectors/network.d.ts +19 -0
  11. package/dist/collectors/network.d.ts.map +1 -0
  12. package/dist/collectors/network.js +114 -0
  13. package/dist/collectors/network.js.map +1 -0
  14. package/dist/connection/cdp.d.ts +99 -0
  15. package/dist/connection/cdp.d.ts.map +1 -0
  16. package/dist/connection/cdp.js +317 -0
  17. package/dist/connection/cdp.js.map +1 -0
  18. package/dist/connection/finder.d.ts +11 -0
  19. package/dist/connection/finder.d.ts.map +1 -0
  20. package/dist/connection/finder.js +23 -0
  21. package/dist/connection/finder.js.map +1 -0
  22. package/dist/connection/launcher.d.ts +30 -0
  23. package/dist/connection/launcher.d.ts.map +1 -0
  24. package/dist/connection/launcher.js +134 -0
  25. package/dist/connection/launcher.js.map +1 -0
  26. package/dist/connection/tabs.d.ts +49 -0
  27. package/dist/connection/tabs.d.ts.map +1 -0
  28. package/dist/connection/tabs.js +216 -0
  29. package/dist/connection/tabs.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +431 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/session/BdgSession.d.ts +62 -0
  35. package/dist/session/BdgSession.d.ts.map +1 -0
  36. package/dist/session/BdgSession.js +172 -0
  37. package/dist/session/BdgSession.js.map +1 -0
  38. package/dist/types.d.ts +159 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/utils/pageLoad.d.ts +21 -0
  43. package/dist/utils/pageLoad.d.ts.map +1 -0
  44. package/dist/utils/pageLoad.js +85 -0
  45. package/dist/utils/pageLoad.js.map +1 -0
  46. package/dist/utils/session.d.ts +104 -0
  47. package/dist/utils/session.d.ts.map +1 -0
  48. package/dist/utils/session.js +244 -0
  49. package/dist/utils/session.js.map +1 -0
  50. package/dist/utils/url.d.ts +6 -0
  51. package/dist/utils/url.d.ts.map +1 -0
  52. package/dist/utils/url.js +11 -0
  53. package/dist/utils/url.js.map +1 -0
  54. package/dist/utils/validation.d.ts +12 -0
  55. package/dist/utils/validation.d.ts.map +1 -0
  56. package/dist/utils/validation.js +20 -0
  57. package/dist/utils/validation.js.map +1 -0
  58. package/package.json +56 -0
@@ -0,0 +1,216 @@
1
+ import { normalizeUrl } from '../utils/url.js';
2
+ /**
3
+ * Score how well a tab matches the search URL.
4
+ * Higher score = better match.
5
+ *
6
+ * @param tab - Target to score
7
+ * @param searchUrl - Normalized URL to match against
8
+ * @returns Match score (0-100)
9
+ */
10
+ function scoreTabMatch(tab, searchUrl) {
11
+ // Exact match = best score
12
+ if (tab.url === searchUrl) {
13
+ return 100;
14
+ }
15
+ // Try URL-based matching
16
+ try {
17
+ const tabUrlObj = new URL(tab.url);
18
+ const searchUrlObj = new URL(searchUrl);
19
+ // Same host + path = excellent score
20
+ if (tabUrlObj.host === searchUrlObj.host &&
21
+ tabUrlObj.pathname === searchUrlObj.pathname) {
22
+ return 90;
23
+ }
24
+ // Same host + path prefix = good score
25
+ if (tabUrlObj.host === searchUrlObj.host &&
26
+ tabUrlObj.pathname.startsWith(searchUrlObj.pathname)) {
27
+ return 70;
28
+ }
29
+ // Same host = decent score
30
+ if (tabUrlObj.host === searchUrlObj.host) {
31
+ return 50;
32
+ }
33
+ }
34
+ catch {
35
+ // URL parsing failed, fall back to substring matching
36
+ }
37
+ // Substring match = weak score
38
+ if (tab.url.includes(searchUrl)) {
39
+ return 30;
40
+ }
41
+ return 0; // No match
42
+ }
43
+ /**
44
+ * Find the best matching target from a list of tabs.
45
+ *
46
+ * @param url - Target URL to find (will be normalized)
47
+ * @param targets - List of available targets
48
+ * @returns Best matching target or null if no match found
49
+ */
50
+ export function findBestTarget(url, targets) {
51
+ const searchUrl = normalizeUrl(url);
52
+ // Filter for page targets only
53
+ const pageTargets = targets.filter((t) => t.type === 'page');
54
+ if (pageTargets.length === 0) {
55
+ return null;
56
+ }
57
+ // Score all targets
58
+ const scored = pageTargets
59
+ .map((t) => ({ target: t, score: scoreTabMatch(t, searchUrl) }))
60
+ .filter(({ score }) => score > 0)
61
+ .sort((a, b) => b.score - a.score);
62
+ if (scored.length === 0) {
63
+ return null;
64
+ }
65
+ // Warn if multiple tabs have same score
66
+ if (scored.length > 1 &&
67
+ scored[0].score === scored[1].score &&
68
+ scored[0].score < 100) {
69
+ console.error(`Warning: Multiple tabs match equally (score ${scored[0].score}), using: ${scored[0].target.url}`);
70
+ }
71
+ return scored[0].target;
72
+ }
73
+ /**
74
+ * Create a new tab with the specified URL using CDP.
75
+ *
76
+ * @param url - URL to open in the new tab
77
+ * @param cdp - CDP connection instance
78
+ * @returns Target information for the new tab
79
+ */
80
+ export async function createNewTab(url, cdp) {
81
+ const normalizedUrl = normalizeUrl(url);
82
+ const port = cdp.getPort();
83
+ // Try CDP method first
84
+ try {
85
+ const response = await cdp.send('Target.createTarget', {
86
+ url: normalizedUrl,
87
+ newWindow: false, // Open as tab, not window
88
+ });
89
+ // Fetch target info from Chrome
90
+ const listResponse = await fetch(`http://127.0.0.1:${port}/json/list`);
91
+ const targets = await listResponse.json();
92
+ // Find the target we just created
93
+ const target = targets.find((t) => t.id === response.targetId);
94
+ if (target) {
95
+ return target;
96
+ }
97
+ }
98
+ catch (cdpError) {
99
+ console.error(`CDP Target.createTarget failed, trying HTTP endpoint...`);
100
+ }
101
+ // Fallback to HTTP endpoint method
102
+ try {
103
+ const createResponse = await fetch(`http://127.0.0.1:${port}/json/new?${encodeURIComponent(normalizedUrl)}`);
104
+ if (!createResponse.ok) {
105
+ throw new Error(`HTTP /json/new failed: ${createResponse.statusText}`);
106
+ }
107
+ const target = await createResponse.json();
108
+ return target;
109
+ }
110
+ catch (httpError) {
111
+ throw new Error(`Failed to create new tab: ${httpError instanceof Error ? httpError.message : String(httpError)}`);
112
+ }
113
+ }
114
+ /**
115
+ * Navigate an existing tab to a new URL using CDP.
116
+ *
117
+ * @param targetId - CDP target ID to navigate
118
+ * @param url - URL to navigate to
119
+ * @param cdp - CDP connection instance
120
+ */
121
+ export async function navigateToUrl(targetId, url, cdp) {
122
+ const normalizedUrl = normalizeUrl(url);
123
+ // Attach to target
124
+ const sessionResponse = await cdp.send('Target.attachToTarget', {
125
+ targetId,
126
+ flatten: true,
127
+ });
128
+ // Navigate
129
+ const navResponse = await cdp.send('Page.navigate', { url: normalizedUrl }, sessionResponse.sessionId);
130
+ if (navResponse.errorText) {
131
+ throw new Error(`Navigation failed: ${navResponse.errorText}`);
132
+ }
133
+ }
134
+ /**
135
+ * Wait for a target to be ready (URL matches expected).
136
+ *
137
+ * @param targetId - CDP target ID to wait for
138
+ * @param expectedUrl - Expected URL (normalized)
139
+ * @param cdp - CDP connection instance
140
+ * @param maxWaitMs - Maximum time to wait in milliseconds
141
+ */
142
+ export async function waitForTargetReady(targetId, expectedUrl, cdp, maxWaitMs = 15000) {
143
+ const startTime = Date.now();
144
+ const normalizedUrl = normalizeUrl(expectedUrl);
145
+ while (Date.now() - startTime < maxWaitMs) {
146
+ try {
147
+ // Fetch current targets
148
+ const response = await fetch(`http://127.0.0.1:${cdp.getPort()}/json/list`);
149
+ const targets = await response.json();
150
+ const target = targets.find((t) => t.id === targetId);
151
+ if (target) {
152
+ // Check if URL matches or is loading
153
+ if (target.url === normalizedUrl ||
154
+ target.url.startsWith(normalizedUrl) ||
155
+ target.url === 'about:blank' // Still loading
156
+ ) {
157
+ // Wait a bit more if still on about:blank
158
+ if (target.url === 'about:blank') {
159
+ await new Promise((resolve) => setTimeout(resolve, 500));
160
+ continue;
161
+ }
162
+ return; // Target is ready!
163
+ }
164
+ }
165
+ }
166
+ catch {
167
+ // Target doesn't exist yet or fetch failed, keep waiting
168
+ }
169
+ await new Promise((resolve) => setTimeout(resolve, 200));
170
+ }
171
+ throw new Error(`Target did not become ready within ${maxWaitMs}ms`);
172
+ }
173
+ /**
174
+ * Create a new tab or find existing tab with the target URL.
175
+ *
176
+ * This is the main entry point for tab management.
177
+ * It tries to find an existing tab first, and if not found,
178
+ * creates a new one.
179
+ *
180
+ * @param url - Target URL
181
+ * @param cdp - CDP connection instance
182
+ * @param reuseTab - If true, navigate existing tab instead of creating new one
183
+ * @returns Target information
184
+ */
185
+ export async function createOrFindTarget(url, cdp, reuseTab = false) {
186
+ // Only look for existing tabs if reuseTab is true
187
+ if (reuseTab) {
188
+ // Try to find existing target via CDP
189
+ const targetsResponse = await cdp.send('Target.getTargets');
190
+ const targets = targetsResponse.targetInfos.map((info) => ({
191
+ id: info.targetId,
192
+ type: info.type,
193
+ url: info.url,
194
+ title: info.title,
195
+ webSocketDebuggerUrl: '', // Not needed for this operation
196
+ }));
197
+ const existingTarget = findBestTarget(url, targets);
198
+ if (existingTarget) {
199
+ console.error(`Found existing tab: ${existingTarget.url}`);
200
+ // If URLs don't match exactly, navigate
201
+ if (existingTarget.url !== normalizeUrl(url)) {
202
+ console.error(`Navigating tab to: ${url}`);
203
+ await navigateToUrl(existingTarget.id, url, cdp);
204
+ await waitForTargetReady(existingTarget.id, url, cdp);
205
+ }
206
+ return existingTarget;
207
+ }
208
+ }
209
+ // No matching tab found (or reuseTab=false), create new one
210
+ console.error(`Creating new tab for: ${url}`);
211
+ const newTarget = await createNewTab(url, cdp);
212
+ // Wait for tab to start loading
213
+ await waitForTargetReady(newTarget.id, url, cdp);
214
+ return newTarget;
215
+ }
216
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/connection/tabs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,431 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { launchChrome, isChromeRunning } from './connection/launcher.js';
4
+ import { BdgSession } from './session/BdgSession.js';
5
+ import { normalizeUrl } from './utils/url.js';
6
+ import { validateCollectorTypes } from './utils/validation.js';
7
+ import { createOrFindTarget } from './connection/tabs.js';
8
+ import { writePid, readPid, isProcessAlive, writeSessionOutput, acquireSessionLock, writeSessionMetadata, cleanupSession } from './utils/session.js';
9
+ const program = new Command();
10
+ // Global state for signal handling
11
+ let session = null;
12
+ let launchedChrome = null;
13
+ let isShuttingDown = false;
14
+ let shutdownKeepalive = null;
15
+ async function handleStop() {
16
+ if (isShuttingDown || !session) {
17
+ return;
18
+ }
19
+ isShuttingDown = true;
20
+ // Keep event loop alive during shutdown to prevent premature exit
21
+ shutdownKeepalive = setInterval(() => { }, 1000);
22
+ try {
23
+ console.error('Stopping session...');
24
+ const output = await session.stop();
25
+ // Write to file for 'bdg stop' to read (synchronous to ensure it completes)
26
+ try {
27
+ console.error('Writing session output...');
28
+ writeSessionOutput(output);
29
+ console.error('Session output written successfully');
30
+ }
31
+ catch (writeError) {
32
+ console.error('Failed to write session output:', writeError);
33
+ console.error('Write error details:', writeError);
34
+ }
35
+ // Also output to stdout (for foreground use)
36
+ console.log(JSON.stringify(output, null, 2));
37
+ // Leave Chrome running for future sessions (persistent profile benefit)
38
+ if (launchedChrome) {
39
+ console.error('Leaving Chrome running for future sessions (use persistent profile)');
40
+ console.error(`Chrome PID: ${launchedChrome.pid}, port: ${launchedChrome.port}`);
41
+ }
42
+ // Cleanup session files
43
+ try {
44
+ cleanupSession();
45
+ }
46
+ catch (cleanupError) {
47
+ console.error('Error cleaning up session:', cleanupError);
48
+ }
49
+ // Clear keepalive and exit
50
+ if (shutdownKeepalive) {
51
+ clearInterval(shutdownKeepalive);
52
+ }
53
+ process.exit(0);
54
+ }
55
+ catch (error) {
56
+ console.error('Error during shutdown:', error);
57
+ const errorOutput = {
58
+ success: false,
59
+ timestamp: new Date().toISOString(),
60
+ duration: 0,
61
+ target: { url: '', title: '' },
62
+ data: {},
63
+ error: error instanceof Error ? error.message : String(error)
64
+ };
65
+ // Write error output to file (synchronous to ensure it completes)
66
+ try {
67
+ console.error('Writing error output...');
68
+ writeSessionOutput(errorOutput);
69
+ console.error('Error output written successfully');
70
+ }
71
+ catch (writeError) {
72
+ console.error('Failed to write error output:', writeError);
73
+ console.error('Write error details:', writeError);
74
+ }
75
+ console.log(JSON.stringify(errorOutput, null, 2));
76
+ // Leave Chrome running even on error
77
+ if (launchedChrome) {
78
+ console.error('Leaving Chrome running (use persistent profile)');
79
+ console.error(`Chrome PID: ${launchedChrome.pid}, port: ${launchedChrome.port}`);
80
+ }
81
+ // Cleanup session files
82
+ try {
83
+ cleanupSession();
84
+ }
85
+ catch (cleanupError) {
86
+ console.error('Error cleaning up session:', cleanupError);
87
+ }
88
+ // Clear keepalive and exit
89
+ if (shutdownKeepalive) {
90
+ clearInterval(shutdownKeepalive);
91
+ }
92
+ process.exit(1);
93
+ }
94
+ }
95
+ // Register signal handlers
96
+ // Wrap in async IIFE to ensure handler completes before exit
97
+ process.on('SIGINT', () => {
98
+ // Prevent multiple signals from being processed
99
+ if (!isShuttingDown) {
100
+ handleStop().catch((error) => {
101
+ console.error('Fatal error during shutdown:', error);
102
+ process.exit(1);
103
+ });
104
+ }
105
+ });
106
+ process.on('SIGTERM', () => {
107
+ if (!isShuttingDown) {
108
+ handleStop().catch((error) => {
109
+ console.error('Fatal error during shutdown:', error);
110
+ process.exit(1);
111
+ });
112
+ }
113
+ });
114
+ async function run(url, options, collectors) {
115
+ const startTime = Date.now();
116
+ try {
117
+ // 1. Acquire session lock
118
+ if (!acquireSessionLock()) {
119
+ const existingPid = readPid();
120
+ throw new Error(`Session already running (PID ${existingPid}). Stop it with: bdg stop`);
121
+ }
122
+ // Validate collector types
123
+ validateCollectorTypes(collectors);
124
+ // Normalize URL - add http:// if no protocol specified
125
+ const targetUrl = normalizeUrl(url);
126
+ // 2. Launch or connect to Chrome
127
+ const chromeRunning = await isChromeRunning(options.port);
128
+ if (!chromeRunning) {
129
+ // Launch Chrome with target URL
130
+ launchedChrome = await launchChrome({
131
+ port: options.port,
132
+ headless: false,
133
+ url: targetUrl,
134
+ userDataDir: options.userDataDir,
135
+ });
136
+ console.error(`Chrome launched (PID: ${launchedChrome.pid})`);
137
+ }
138
+ else {
139
+ console.error(`Chrome already running on port ${options.port}`);
140
+ }
141
+ // 3. Create session (connects to CDP)
142
+ // We need a temporary target just to connect to CDP
143
+ // Then we'll use createOrFindTarget to get the right tab
144
+ const tempResponse = await fetch(`http://127.0.0.1:${options.port}/json/list`);
145
+ const tempTargets = await tempResponse.json();
146
+ if (tempTargets.length === 0) {
147
+ throw new Error('No targets available in Chrome');
148
+ }
149
+ // Use first available target to establish CDP connection
150
+ session = new BdgSession(tempTargets[0], options.port);
151
+ await session.connect();
152
+ if (!session.isConnected()) {
153
+ throw new Error('Failed to establish CDP connection');
154
+ }
155
+ // 4. Create or find target tab using TabManager
156
+ console.error(`Finding or creating tab for: ${targetUrl}`);
157
+ const target = await createOrFindTarget(url, session.getCDP(), options.reuseTab ?? false);
158
+ console.error(`Using tab: ${target.url}`);
159
+ // Fetch full target info with webSocketDebuggerUrl
160
+ const fullTargetResponse = await fetch(`http://127.0.0.1:${options.port}/json/list`);
161
+ const fullTargets = await fullTargetResponse.json();
162
+ const fullTarget = fullTargets.find((t) => t.id === target.id);
163
+ if (!fullTarget || !fullTarget.webSocketDebuggerUrl) {
164
+ throw new Error(`Could not find webSocketDebuggerUrl for target ${target.id}`);
165
+ }
166
+ // Update session with the correct target
167
+ // Close current session and reconnect to the correct target
168
+ await session.getCDP().close();
169
+ session = new BdgSession(fullTarget, options.port);
170
+ await session.connect();
171
+ // 5. Start collectors
172
+ for (const collector of collectors) {
173
+ await session.startCollector(collector);
174
+ }
175
+ // 6. Write session metadata
176
+ writePid(process.pid);
177
+ writeSessionMetadata({
178
+ bdgPid: process.pid,
179
+ chromePid: launchedChrome?.pid,
180
+ startTime,
181
+ port: options.port,
182
+ targetId: fullTarget.id,
183
+ webSocketDebuggerUrl: fullTarget.webSocketDebuggerUrl,
184
+ });
185
+ const collectorNames = collectors.length === 3
186
+ ? 'network, console, and DOM'
187
+ : collectors.join(', ');
188
+ if (options.timeout) {
189
+ console.error(`Collecting ${collectorNames}... (Ctrl+C to stop and output, or wait ${options.timeout}s for timeout)`);
190
+ // Set timeout if explicitly provided
191
+ setTimeout(() => {
192
+ console.error(`\nTimeout reached (${options.timeout}s)`);
193
+ handleStop();
194
+ }, options.timeout * 1000);
195
+ }
196
+ else {
197
+ console.error(`Collecting ${collectorNames}... (Ctrl+C to stop and output, or use 'bdg stop')`);
198
+ }
199
+ // 7. Keep alive until signal or error
200
+ await new Promise((_, reject) => {
201
+ if (!session) {
202
+ reject(new Error('Session not initialized'));
203
+ return;
204
+ }
205
+ // Listen for WebSocket connection loss
206
+ const connectionCheckInterval = setInterval(() => {
207
+ if (!session) {
208
+ clearInterval(connectionCheckInterval);
209
+ return;
210
+ }
211
+ if (!session.isConnected()) {
212
+ clearInterval(connectionCheckInterval);
213
+ reject(new Error('WebSocket connection lost'));
214
+ return;
215
+ }
216
+ }, 2000); // Check every 2 seconds
217
+ // Listen for target destruction (tab closed/navigated)
218
+ session.getCDP().on('Target.targetDestroyed', (params) => {
219
+ if (params.targetId === target.id) {
220
+ clearInterval(connectionCheckInterval);
221
+ reject(new Error('Browser tab was closed'));
222
+ }
223
+ });
224
+ });
225
+ }
226
+ catch (error) {
227
+ const errorOutput = {
228
+ success: false,
229
+ timestamp: new Date().toISOString(),
230
+ duration: Date.now() - startTime,
231
+ target: { url: '', title: '' },
232
+ data: {},
233
+ error: error instanceof Error ? error.message : String(error)
234
+ };
235
+ console.log(JSON.stringify(errorOutput, null, 2));
236
+ // Cleanup on error
237
+ if (launchedChrome) {
238
+ try {
239
+ await launchedChrome.kill();
240
+ }
241
+ catch { }
242
+ }
243
+ cleanupSession();
244
+ process.exit(1);
245
+ }
246
+ }
247
+ program
248
+ .name('bdg')
249
+ .description('Browser telemetry via Chrome DevTools Protocol')
250
+ .version('0.1.0');
251
+ program
252
+ .argument('<url>', 'Target URL (example.com or localhost:3000)')
253
+ .option('-p, --port <number>', 'Chrome debugging port', '9222')
254
+ .option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
255
+ .option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
256
+ .option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
257
+ .action(async (url, options) => {
258
+ const port = parseInt(options.port);
259
+ const timeout = options.timeout ? parseInt(options.timeout) : undefined;
260
+ const reuseTab = options.reuseTab ?? false;
261
+ const userDataDir = options.userDataDir;
262
+ await run(url, { port, timeout, reuseTab, userDataDir }, ['dom', 'network', 'console']);
263
+ });
264
+ program
265
+ .command('dom')
266
+ .description('Collect DOM only')
267
+ .argument('<url>', 'Target URL')
268
+ .option('-p, --port <number>', 'Chrome debugging port', '9222')
269
+ .option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
270
+ .option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
271
+ .option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
272
+ .action(async (url, options) => {
273
+ const port = parseInt(options.port);
274
+ const timeout = options.timeout ? parseInt(options.timeout) : undefined;
275
+ const reuseTab = options.reuseTab ?? false;
276
+ const userDataDir = options.userDataDir;
277
+ await run(url, { port, timeout, reuseTab, userDataDir }, ['dom']);
278
+ });
279
+ program
280
+ .command('network')
281
+ .description('Collect network requests only')
282
+ .argument('<url>', 'Target URL')
283
+ .option('-p, --port <number>', 'Chrome debugging port', '9222')
284
+ .option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
285
+ .option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
286
+ .option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
287
+ .action(async (url, options) => {
288
+ const port = parseInt(options.port);
289
+ const timeout = options.timeout ? parseInt(options.timeout) : undefined;
290
+ const reuseTab = options.reuseTab ?? false;
291
+ const userDataDir = options.userDataDir;
292
+ await run(url, { port, timeout, reuseTab, userDataDir }, ['network']);
293
+ });
294
+ program
295
+ .command('console')
296
+ .description('Collect console logs only')
297
+ .argument('<url>', 'Target URL')
298
+ .option('-p, --port <number>', 'Chrome debugging port', '9222')
299
+ .option('-t, --timeout <seconds>', 'Auto-stop after timeout (optional)')
300
+ .option('-r, --reuse-tab', 'Navigate existing tab instead of creating new one')
301
+ .option('-u, --user-data-dir <path>', 'Chrome user data directory (default: ~/.bdg/chrome-profile)')
302
+ .action(async (url, options) => {
303
+ const port = parseInt(options.port);
304
+ const timeout = options.timeout ? parseInt(options.timeout) : undefined;
305
+ const reuseTab = options.reuseTab ?? false;
306
+ const userDataDir = options.userDataDir;
307
+ await run(url, { port, timeout, reuseTab, userDataDir }, ['console']);
308
+ });
309
+ program
310
+ .command('query')
311
+ .description('Execute JavaScript in the active session for live debugging')
312
+ .argument('<script>', 'JavaScript to execute (e.g., "document.querySelector(\'input[type=email]\').value")')
313
+ .option('-p, --port <number>', 'Chrome debugging port', '9222')
314
+ .action(async (script, options) => {
315
+ try {
316
+ const port = parseInt(options.port);
317
+ // Check if session is running
318
+ const pid = readPid();
319
+ if (!pid || !isProcessAlive(pid)) {
320
+ console.error('Error: No active session running');
321
+ console.error('Start a session with: bdg <url>');
322
+ process.exit(1);
323
+ }
324
+ // Read session metadata to get the target ID
325
+ const { readSessionMetadata } = await import('./utils/session.js');
326
+ const metadata = readSessionMetadata();
327
+ if (!metadata || !metadata.targetId || !metadata.webSocketDebuggerUrl) {
328
+ console.error('Error: No target information in session metadata');
329
+ console.error('Session may have been started with an older version');
330
+ process.exit(1);
331
+ }
332
+ // Verify the target still exists
333
+ const response = await fetch(`http://127.0.0.1:${port}/json/list`);
334
+ const targets = await response.json();
335
+ const target = targets.find((t) => t.id === metadata.targetId);
336
+ if (!target) {
337
+ console.error('Error: Session target not found (tab may have been closed)');
338
+ console.error('Start a new session with: bdg <url>');
339
+ process.exit(1);
340
+ }
341
+ // Create temporary CDP connection using stored webSocketDebuggerUrl
342
+ const { CDPConnection } = await import('./connection/cdp.js');
343
+ const cdp = new CDPConnection();
344
+ await cdp.connect(metadata.webSocketDebuggerUrl);
345
+ // Execute JavaScript
346
+ const result = await cdp.send('Runtime.evaluate', {
347
+ expression: script,
348
+ returnByValue: true,
349
+ awaitPromise: true
350
+ });
351
+ await cdp.close();
352
+ // Output result
353
+ if (result.exceptionDetails) {
354
+ console.error('Error executing script:');
355
+ console.error(result.exceptionDetails.exception.description);
356
+ process.exit(1);
357
+ }
358
+ console.log(JSON.stringify(result.result.value, null, 2));
359
+ process.exit(0);
360
+ }
361
+ catch (error) {
362
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
363
+ process.exit(1);
364
+ }
365
+ });
366
+ program
367
+ .command('stop')
368
+ .description('Stop all active sessions and free ports (does not capture output)')
369
+ .option('--kill-chrome', 'Also kill Chrome browser')
370
+ .action(async (options) => {
371
+ try {
372
+ const { readSessionMetadata } = await import('./utils/session.js');
373
+ // Read PID
374
+ const pid = readPid();
375
+ if (!pid) {
376
+ console.error('No active session found');
377
+ console.error('All ports should be free');
378
+ process.exit(0);
379
+ }
380
+ console.error(`Stopping session (PID ${pid})...`);
381
+ // Read metadata BEFORE killing the process (so we can get Chrome PID)
382
+ const metadata = readSessionMetadata();
383
+ // Kill the bdg process (use SIGKILL for immediate termination)
384
+ if (isProcessAlive(pid)) {
385
+ try {
386
+ process.kill(pid, 'SIGKILL');
387
+ console.error(`✓ Killed bdg session (PID ${pid})`);
388
+ }
389
+ catch (killError) {
390
+ console.error(`Warning: Could not kill process ${pid}:`, killError);
391
+ }
392
+ }
393
+ else {
394
+ console.error(`Process ${pid} already stopped`);
395
+ }
396
+ // Kill Chrome if requested
397
+ if (options.killChrome) {
398
+ if (metadata?.chromePid) {
399
+ try {
400
+ if (isProcessAlive(metadata.chromePid)) {
401
+ process.kill(metadata.chromePid, 'SIGTERM');
402
+ console.error(`✓ Killed Chrome (PID ${metadata.chromePid})`);
403
+ }
404
+ else {
405
+ console.error(`Chrome process (PID ${metadata.chromePid}) already stopped`);
406
+ }
407
+ }
408
+ catch (chromeError) {
409
+ console.error(`Warning: Could not kill Chrome:`, chromeError);
410
+ }
411
+ }
412
+ else {
413
+ console.error('Warning: Chrome PID not found in session metadata');
414
+ }
415
+ }
416
+ else {
417
+ console.error('Leaving Chrome running (use --kill-chrome to close it)');
418
+ }
419
+ // Clean up session files
420
+ cleanupSession();
421
+ console.error('✓ Cleaned up session files');
422
+ console.error('\nAll sessions stopped and ports freed');
423
+ process.exit(0);
424
+ }
425
+ catch (error) {
426
+ console.error(`Error stopping session: ${error instanceof Error ? error.message : String(error)}`);
427
+ process.exit(1);
428
+ }
429
+ });
430
+ program.parse();
431
+ //# sourceMappingURL=index.js.map