@crouton-kit/capture 1.3.2 → 1.3.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.
@@ -0,0 +1,121 @@
1
+ import { recordTraffic, navigateAndRecord } from '../record.js';
2
+ import { withConnection } from '../connection.js';
3
+ import { appendToHarRecording as appendToHar } from '../../har-manager.js';
4
+ import { type ParsedArgs } from '../types.js';
5
+
6
+ export async function cmdRecord(parsed: ParsedArgs, _args: string[]): Promise<void> {
7
+ if (parsed.help) {
8
+ console.log(
9
+ 'Usage: capture record --target <id> [--duration <secs>] [--har-out <path>]\n\n' +
10
+ 'Passive HAR recording for the specified duration (default: 10s).',
11
+ );
12
+ process.exit(0);
13
+ }
14
+ if (!parsed.target) {
15
+ console.error(
16
+ 'Usage: capture record --target <id> [--duration <secs>] [--har-out <path>]',
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ const result = await recordTraffic({
22
+ port: parsed.port,
23
+ targetId: parsed.target,
24
+ duration: parsed.duration,
25
+ harOutPath: parsed.harOut,
26
+ });
27
+ console.log(
28
+ JSON.stringify(
29
+ { entryCount: result.entryCount, harPath: result.harPath },
30
+ null,
31
+ 2,
32
+ ),
33
+ );
34
+ return;
35
+ }
36
+
37
+ export async function cmdNavigate(parsed: ParsedArgs, _args: string[]): Promise<void> {
38
+ if (parsed.help) {
39
+ console.log(
40
+ 'Usage: capture navigate <url> [--har-out <path>] [--settle <ms>] [--har <id>]\n\n' +
41
+ 'Navigate to URL and record HAR. Appends to session HAR if --har is set.\n\n' +
42
+ 'Example: capture navigate "https://app.example.com/dashboard" --settle 3000',
43
+ );
44
+ process.exit(0);
45
+ }
46
+ const url = parsed.positional[0];
47
+ if (!url) {
48
+ console.error(
49
+ 'Usage: capture navigate <url> [--har-out <path>] [--settle <ms>]',
50
+ );
51
+ process.exit(1);
52
+ }
53
+
54
+ const result = await navigateAndRecord({
55
+ port: parsed.port,
56
+ url,
57
+ targetId: parsed.target,
58
+ harOutPath: parsed.har ? undefined : parsed.harOut,
59
+ settle: parsed.settle,
60
+ });
61
+
62
+ // Append to session HAR if active
63
+ if (parsed.har && result.har.log.entries.length > 0) {
64
+ appendToHar(parsed.har, result.har.log.entries);
65
+ console.error(` [har:${parsed.har}] +${result.har.log.entries.length} entries`);
66
+ }
67
+
68
+ console.log(
69
+ JSON.stringify(
70
+ {
71
+ entryCount: result.entryCount,
72
+ harPath: result.harPath,
73
+ tabUrl: result.tab.url,
74
+ timedOut: result.timedOut,
75
+ },
76
+ null,
77
+ 2,
78
+ ),
79
+ );
80
+ return;
81
+ }
82
+
83
+ export async function cmdNetwork(parsed: ParsedArgs, _args: string[]): Promise<void> {
84
+ if (parsed.help) {
85
+ console.log(
86
+ 'Usage: capture network <offline|online> [--target <id>]\n\n' +
87
+ 'Toggle network connectivity for a tab. Use "offline" to simulate\n' +
88
+ 'network failure (kills WebSocket connections, blocks HTTP requests).\n' +
89
+ 'Use "online" to restore connectivity.',
90
+ );
91
+ process.exit(0);
92
+ }
93
+ const mode = parsed.positional[0];
94
+ if (!mode || !['offline', 'online'].includes(mode)) {
95
+ console.error('Usage: capture network <offline|online> [--target <id>]');
96
+ process.exit(1);
97
+ }
98
+ const offline = mode === 'offline';
99
+ const result = await withConnection(
100
+ parsed,
101
+ async (client) => {
102
+ await client.send('Network.enable');
103
+ await client.send('Network.emulateNetworkConditions', {
104
+ offline,
105
+ latency: offline ? -1 : 0,
106
+ downloadThroughput: offline ? 0 : -1,
107
+ uploadThroughput: offline ? 0 : -1,
108
+ });
109
+ return { network: mode, offline };
110
+ },
111
+ { settle: 0 },
112
+ );
113
+ console.log(JSON.stringify(result, null, 2));
114
+ if (offline) {
115
+ console.error('\nNetwork disabled. WebSocket connections will drop.');
116
+ console.error('Restore with: capture network online');
117
+ } else {
118
+ console.error('\nNetwork restored.');
119
+ }
120
+ return;
121
+ }
@@ -0,0 +1,172 @@
1
+ import * as fs from 'fs';
2
+ import { withConnection } from '../connection.js';
3
+ import { captureScreenshot, autoScreenshot } from '../screenshot.js';
4
+ import { getAccessibilityTree, flattenA11yTree } from '../a11y.js';
5
+ import { clickByName, typeText, focusAndType } from '../../interact.js';
6
+ import { nextStepPath } from '../../session-context.js';
7
+ import { type ParsedArgs } from '../types.js';
8
+
9
+ export async function cmdScreenshot(parsed: ParsedArgs, _args: string[]): Promise<void> {
10
+ if (parsed.help) {
11
+ console.log(
12
+ 'Usage: capture screenshot [--target <id>] [--out <path>] [--viewport <preset>] [--height <px>] [--full-page]\n\n' +
13
+ 'Capture a screenshot of the targeted tab. Saves to --out or auto-generates a path.\n\n' +
14
+ 'Options:\n' +
15
+ ' --viewport <preset> Viewport preset (default: desktop)\n' +
16
+ ' desktop-wide 1920x1080\n' +
17
+ ' desktop 1280x800\n' +
18
+ ' tablet 768x1024\n' +
19
+ ' mobile 390x844\n' +
20
+ ' --height <px> Override viewport height (e.g. 1600)\n' +
21
+ ' --full-page Capture the entire scrollable page',
22
+ );
23
+ process.exit(0);
24
+ }
25
+ const VIEWPORTS: Record<string, { width: number; height: number }> = {
26
+ 'desktop-wide': { width: 1920, height: 1080 },
27
+ 'desktop': { width: 1280, height: 800 },
28
+ 'tablet': { width: 768, height: 1024 },
29
+ 'mobile': { width: 390, height: 844 },
30
+ };
31
+ const viewportName = parsed.viewport ?? 'desktop';
32
+ const viewport = VIEWPORTS[viewportName];
33
+ if (!viewport) {
34
+ console.error(`Unknown viewport "${viewportName}". Options: ${Object.keys(VIEWPORTS).join(', ')}`);
35
+ process.exit(1);
36
+ }
37
+ // Allow --height to override the viewport preset height
38
+ if (parsed.height) {
39
+ viewport.height = parsed.height;
40
+ }
41
+ const result = await withConnection(
42
+ parsed,
43
+ async (client) => {
44
+ const png = await captureScreenshot(client, viewport, { fullPage: parsed.fullPage });
45
+ const outPath = parsed.out
46
+ ? parsed.out
47
+ : (nextStepPath('screenshot', 'manual') ?? `/tmp/capture-screenshot-${Date.now()}.png`);
48
+ fs.writeFileSync(outPath, png);
49
+ return { path: outPath, bytes: png.length };
50
+ },
51
+ { settle: 0 },
52
+ );
53
+ console.log(JSON.stringify(result, null, 2));
54
+ return;
55
+ }
56
+
57
+ export async function cmdClick(parsed: ParsedArgs, _args: string[]): Promise<void> {
58
+ if (parsed.help) {
59
+ console.log(
60
+ 'Usage: capture click "name" [--role button|link|...] [--no-screenshot]\n\n' +
61
+ 'Click an element by its accessible name. Auto-captures a screenshot after clicking.',
62
+ );
63
+ process.exit(0);
64
+ }
65
+ const name = parsed.positional[0];
66
+ if (!name) {
67
+ console.error('Usage: capture click "name" [--role button|link|...] [--no-screenshot]');
68
+ process.exit(1);
69
+ }
70
+ const clickSettle = parsed.settle !== undefined ? parsed.settle : (parsed.har ? 2500 : 1000);
71
+ const result = await withConnection(
72
+ parsed,
73
+ async (client) => {
74
+ const clickResult = await clickByName(client, name, parsed.role);
75
+ const screenshot = await autoScreenshot(client, 'click', name, parsed.noScreenshot);
76
+ return { ...clickResult, screenshot };
77
+ },
78
+ { settle: clickSettle },
79
+ );
80
+ console.log(JSON.stringify(result, null, 2));
81
+ return;
82
+ }
83
+
84
+ export async function cmdType(parsed: ParsedArgs, _args: string[]): Promise<void> {
85
+ if (parsed.help) {
86
+ console.log(
87
+ 'Usage: capture type "text" [--into "field name"] [--no-screenshot]\n\n' +
88
+ 'Type text into the focused element, or into a named field with --into.',
89
+ );
90
+ process.exit(0);
91
+ }
92
+ const text = parsed.positional[0];
93
+ if (!text) {
94
+ console.error('Usage: capture type "text" [--into "field name"] [--no-screenshot]');
95
+ process.exit(1);
96
+ }
97
+ const result = await withConnection(
98
+ parsed,
99
+ async (client) => {
100
+ let field: string | null = null;
101
+ if (parsed.into) {
102
+ await focusAndType(client, parsed.into, text, parsed.role);
103
+ field = parsed.into;
104
+ } else {
105
+ await typeText(client, text);
106
+ }
107
+ const label = field ?? text;
108
+ const screenshot = await autoScreenshot(client, 'type', label, parsed.noScreenshot);
109
+ return { typed: text, field, screenshot };
110
+ },
111
+ { settle: parsed.settle !== undefined ? parsed.settle : (parsed.har ? 1500 : 500) },
112
+ );
113
+ console.log(JSON.stringify(result, null, 2));
114
+ return;
115
+ }
116
+
117
+ export async function cmdA11y(parsed: ParsedArgs, _args: string[]): Promise<void> {
118
+ if (parsed.help) {
119
+ console.log(
120
+ 'Usage: capture a11y [--target <id>] [--json] [--interactive]\n\n' +
121
+ 'Get the accessibility tree. Use --interactive for interactive elements only.\n' +
122
+ 'Use --json for structured JSON output.',
123
+ );
124
+ process.exit(0);
125
+ }
126
+ const result = await withConnection(
127
+ parsed,
128
+ async (client) => {
129
+ const tree = await getAccessibilityTree(client);
130
+
131
+ if (parsed.json) {
132
+ return tree;
133
+ }
134
+
135
+ // Flat text output
136
+ const lines = flattenA11yTree(tree, {
137
+ interactive: parsed.interactive,
138
+ });
139
+ return lines.join('\n');
140
+ },
141
+ { settle: 0 },
142
+ );
143
+
144
+ if (parsed.json) {
145
+ console.log(JSON.stringify(result, null, 2));
146
+ } else {
147
+ console.log(result);
148
+ }
149
+
150
+ // Hint when a11y tree is sparse
151
+ if (!parsed.json) {
152
+ const lineCount = typeof result === 'string' ? result.split('\n').filter(Boolean).length : 0;
153
+ if (lineCount === 0) {
154
+ console.error(
155
+ '\nNo elements found.' +
156
+ (parsed.interactive ? ' Try without --interactive to see all nodes.' : '') +
157
+ '\n\nIf this page is part of your project, consider adding ARIA attributes:\n' +
158
+ ' - role="button|link|textbox" on interactive elements\n' +
159
+ ' - aria-label="..." on elements without visible text\n' +
160
+ ' - Semantic HTML (<button>, <input>, <nav>) provides roles automatically',
161
+ );
162
+ } else if (parsed.interactive && lineCount < 5) {
163
+ console.error(
164
+ `\nOnly ${lineCount} interactive element${lineCount === 1 ? '' : 's'} found. ` +
165
+ 'If this seems low, the page may lack ARIA markup.\n' +
166
+ 'Consider adding role and aria-label attributes to interactive elements in your frontend code.',
167
+ );
168
+ }
169
+ }
170
+
171
+ return;
172
+ }
@@ -0,0 +1,108 @@
1
+ import * as fs from 'fs';
2
+ import { CDPClient } from './client.js';
3
+ import { type CDPTarget, type ParsedArgs } from './types.js';
4
+ import { findTabById } from './targets.js';
5
+ import { detectCdpPortsAsync } from './detect.js';
6
+ import { ConsoleRecorder, printConsoleSummary } from './console-recorder.js';
7
+ import { HARRecorder } from './har-recorder.js';
8
+ import {
9
+ harFilePath,
10
+ appendToHarRecording as appendToHar,
11
+ } from '../har-manager.js';
12
+ import { getActiveSession, updateActiveSession } from '../session-context.js';
13
+
14
+ export async function connectForCommand(
15
+ parsed: ParsedArgs,
16
+ ): Promise<{ client: CDPClient; tab: CDPTarget }> {
17
+ if (!parsed.target) {
18
+ throw new Error('Use --target <tabId> to target a tab. Run "capture list" to see available tabs.');
19
+ }
20
+
21
+ // If --port is explicit, search only that port. Otherwise search all endpoints.
22
+ let tab: CDPTarget | null = null;
23
+ if (parsed.port) {
24
+ tab = await findTabById(parsed.port, parsed.target);
25
+ } else {
26
+ const endpoints = await detectCdpPortsAsync();
27
+ for (const ep of endpoints) {
28
+ try {
29
+ tab = await findTabById(ep.port, parsed.target);
30
+ if (tab) break;
31
+ } catch {
32
+ // Skip endpoints that fail
33
+ }
34
+ }
35
+ }
36
+
37
+ if (!tab) {
38
+ const query = parsed.target;
39
+ throw new Error(
40
+ `No tab found for target "${query}". Run "capture list" to see available tabs.`,
41
+ );
42
+ }
43
+
44
+ if (!tab.webSocketDebuggerUrl) {
45
+ throw new Error('Tab has no WebSocket debugger URL');
46
+ }
47
+
48
+ // Lazy-populate targetId in active session if not yet set
49
+ const activeSession = getActiveSession();
50
+ if (activeSession && !activeSession.targetId) {
51
+ updateActiveSession({ targetId: tab.id });
52
+ }
53
+
54
+ const client = new CDPClient(tab.webSocketDebuggerUrl);
55
+ await client.waitReady();
56
+
57
+ return { client, tab };
58
+ }
59
+
60
+ export async function withConnection<T>(
61
+ parsed: ParsedArgs,
62
+ fn: (client: CDPClient, tab: CDPTarget) => Promise<T>,
63
+ opts: { settle?: number } = {},
64
+ ): Promise<T> {
65
+ const { client, tab } = await connectForCommand(parsed);
66
+
67
+ const consoleRecorder = new ConsoleRecorder(client);
68
+ await consoleRecorder.start();
69
+
70
+ let harRecorder: HARRecorder | undefined;
71
+ if (parsed.har) {
72
+ // Validate HAR ID exists before starting recording
73
+ const harPath = harFilePath(parsed.har);
74
+ if (!fs.existsSync(harPath)) {
75
+ console.error(
76
+ `ERROR: No HAR recording found for --har "${parsed.har}". Run 'har create' first.`,
77
+ );
78
+ process.exit(1);
79
+ }
80
+ harRecorder = new HARRecorder(client);
81
+ await harRecorder.start();
82
+ }
83
+
84
+ try {
85
+ const result = await fn(client, tab);
86
+
87
+ // Wait for network activity triggered by the action, then append to HAR
88
+ if (harRecorder && parsed.har) {
89
+ const settle = opts.settle !== undefined ? opts.settle : 3000;
90
+ if (settle > 0) {
91
+ await new Promise((r) => setTimeout(r, settle));
92
+ }
93
+ const har = await harRecorder.finish();
94
+ if (har.log.entries.length > 0) {
95
+ appendToHar(parsed.har, har.log.entries);
96
+ console.error(
97
+ ` [har:${parsed.har}] +${har.log.entries.length} entries`,
98
+ );
99
+ }
100
+ }
101
+
102
+ printConsoleSummary(consoleRecorder.finish());
103
+
104
+ return result;
105
+ } finally {
106
+ client.close();
107
+ }
108
+ }
@@ -0,0 +1,80 @@
1
+ import { type CDPClient } from './client.js';
2
+
3
+ export interface ConsoleEntry {
4
+ level: 'log' | 'warning' | 'error' | 'info' | 'debug';
5
+ text: string;
6
+ timestamp: number;
7
+ source?: string;
8
+ }
9
+
10
+ export class ConsoleRecorder {
11
+ private entries: ConsoleEntry[] = [];
12
+
13
+ constructor(private client: CDPClient) {}
14
+
15
+ async start(): Promise<void> {
16
+ await this.client.send('Runtime.enable');
17
+
18
+ this.client.on('Runtime.consoleAPICalled', (params: unknown) => {
19
+ const p = params as {
20
+ type: string;
21
+ args: Array<{ type: string; value?: unknown; description?: string }>;
22
+ timestamp: number;
23
+ };
24
+ const text = p.args
25
+ .map((a) =>
26
+ a.value !== undefined ? String(a.value) : (a.description ?? ''),
27
+ )
28
+ .join(' ');
29
+ this.entries.push({
30
+ level: p.type as ConsoleEntry['level'],
31
+ text,
32
+ timestamp: p.timestamp,
33
+ });
34
+ });
35
+
36
+ this.client.on('Runtime.exceptionThrown', (params: unknown) => {
37
+ const p = params as {
38
+ timestamp: number;
39
+ exceptionDetails: {
40
+ exception?: { description?: string };
41
+ text?: string;
42
+ url?: string;
43
+ lineNumber?: number;
44
+ };
45
+ };
46
+ this.entries.push({
47
+ level: 'error',
48
+ text:
49
+ p.exceptionDetails.exception?.description ??
50
+ p.exceptionDetails.text ??
51
+ 'Unknown exception',
52
+ timestamp: p.timestamp,
53
+ source: p.exceptionDetails.url
54
+ ? `${p.exceptionDetails.url}:${p.exceptionDetails.lineNumber}`
55
+ : undefined,
56
+ });
57
+ });
58
+ }
59
+
60
+ finish(): ConsoleEntry[] {
61
+ return [...this.entries];
62
+ }
63
+ }
64
+
65
+ export function printConsoleSummary(entries: ConsoleEntry[]): void {
66
+ const errors = entries.filter((e) => e.level === 'error');
67
+ const warnings = entries.filter((e) => e.level === 'warning');
68
+ if (errors.length > 0 || warnings.length > 0) {
69
+ console.error(
70
+ `\nConsole: ${errors.length} errors, ${warnings.length} warnings`,
71
+ );
72
+ for (const entry of [...errors, ...warnings]) {
73
+ const prefix = entry.level === 'error' ? 'ERR' : 'WARN';
74
+ const src = entry.source ? ` (${entry.source})` : '';
75
+ const text =
76
+ entry.text.length > 200 ? entry.text.slice(0, 197) + '...' : entry.text;
77
+ console.error(` [${prefix}] ${text}${src}`);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,150 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ export const BROWSER_PATTERNS: Record<string, string[]> = {
4
+ 'company.thebrowser.browser': ['arc'],
5
+ 'com.google.chrome': ['chrome', 'google chrome'],
6
+ 'com.brave.browser': ['brave'],
7
+ 'com.microsoft.edgemac': ['edge', 'microsoft edge'],
8
+ 'org.chromium.chromium': ['chromium'],
9
+ };
10
+
11
+ export function getDefaultBrowserId(): string | null {
12
+ try {
13
+ const output = execSync(
14
+ 'defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers 2>/dev/null',
15
+ { encoding: 'utf-8' },
16
+ );
17
+ const match = output.match(
18
+ /LSHandlerRoleAll = "([^"]+)";\s*LSHandlerURLScheme = https;/,
19
+ );
20
+ return match?.[1] ?? null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ interface CdpProbeResult {
27
+ browser: string;
28
+ app: string;
29
+ isElectron: boolean;
30
+ }
31
+
32
+ async function probeCdpPort(port: number): Promise<CdpProbeResult | null> {
33
+ try {
34
+ const controller = new AbortController();
35
+ const timeout = setTimeout(() => controller.abort(), 500);
36
+ const resp = await fetch(`http://localhost:${port}/json/version`, {
37
+ signal: controller.signal,
38
+ });
39
+ clearTimeout(timeout);
40
+ if (resp.ok) {
41
+ const version = (await resp.json()) as {
42
+ Browser?: string;
43
+ 'User-Agent'?: string;
44
+ };
45
+ const browser = version.Browser;
46
+ const userAgent = version['User-Agent'] ?? '';
47
+ if (!browser) return null;
48
+
49
+ const isElectron = userAgent.includes('Electron');
50
+
51
+ // Extract app name from User-Agent for Electron apps
52
+ // Pattern: "AppName/1.2.3 Chrome/..." in the UA string
53
+ let app = browser;
54
+ if (isElectron) {
55
+ const appMatch = userAgent.match(
56
+ /(\S+?)\/[\d.]+ Chrome\/[\d.]+ Electron/,
57
+ );
58
+ if (appMatch) app = appMatch[1];
59
+ }
60
+
61
+ return { browser, app, isElectron };
62
+ }
63
+ } catch {
64
+ // Not a CDP port or not responding
65
+ }
66
+ return null;
67
+ }
68
+
69
+ function getLocalhostListeningPorts(): number[] {
70
+ try {
71
+ // netstat is much faster than lsof on macOS
72
+ const output = execSync(
73
+ 'netstat -an 2>/dev/null | grep "127.0.0.1.*LISTEN"',
74
+ { encoding: 'utf-8' },
75
+ );
76
+ const ports: number[] = [];
77
+ for (const line of output.split('\n')) {
78
+ // Format: tcp4 0 0 127.0.0.1.56192 *.* LISTEN
79
+ const match = line.match(/127\.0\.0\.1\.(\d+)/);
80
+ if (match) {
81
+ ports.push(parseInt(match[1], 10));
82
+ }
83
+ }
84
+ return ports;
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+
90
+ export interface CdpEndpoint {
91
+ port: number;
92
+ app: string;
93
+ bundleId: string;
94
+ isElectron: boolean;
95
+ }
96
+
97
+ export async function detectCdpPortsAsync(): Promise<CdpEndpoint[]> {
98
+ const ports = getLocalhostListeningPorts();
99
+ const results: CdpEndpoint[] = [];
100
+
101
+ // Probe ports in parallel for speed
102
+ const probes = ports.map(async (port) => {
103
+ const probe = await probeCdpPort(port);
104
+ if (probe) {
105
+ // Map browser string to bundle ID for default browser matching
106
+ const lower = probe.browser.toLowerCase();
107
+ let bundleId = 'unknown';
108
+ for (const [bid, patterns] of Object.entries(BROWSER_PATTERNS)) {
109
+ if (patterns.some((p) => lower.includes(p))) {
110
+ bundleId = bid;
111
+ break;
112
+ }
113
+ }
114
+ results.push({
115
+ port,
116
+ app: probe.app,
117
+ bundleId,
118
+ isElectron: probe.isElectron,
119
+ });
120
+ }
121
+ });
122
+
123
+ await Promise.all(probes);
124
+ return results;
125
+ }
126
+
127
+ export async function detectCdpPort(): Promise<number> {
128
+ const endpoints = await detectCdpPortsAsync();
129
+ if (endpoints.length === 0) {
130
+ throw new Error(
131
+ 'No browser with CDP found. Start your browser with remote debugging enabled:\n' +
132
+ ' Arc: Already enabled by default\n' +
133
+ ' Chrome: --remote-debugging-port=9222\n' +
134
+ ' Electron apps expose CDP automatically',
135
+ );
136
+ }
137
+
138
+ // Use default browser if available (prefer non-Electron)
139
+ const defaultBrowser = getDefaultBrowserId();
140
+ if (defaultBrowser) {
141
+ const match = endpoints.find(
142
+ (p) => p.bundleId === defaultBrowser && !p.isElectron,
143
+ );
144
+ if (match) return match.port;
145
+ }
146
+
147
+ // Fall back to first non-Electron, then first found
148
+ const nonElectron = endpoints.find((p) => !p.isElectron);
149
+ return (nonElectron ?? endpoints[0]).port;
150
+ }
@@ -0,0 +1,30 @@
1
+ import { parseCliArgs } from './args.js';
2
+ import { CDP_USAGE } from './usage.js';
3
+ import { cmdDetect, cmdList, cmdOpen, cmdResetTab } from './commands/tabs.js';
4
+ import { cmdExec } from './commands/exec.js';
5
+ import { cmdScreenshot, cmdClick, cmdType, cmdA11y } from './commands/ui.js';
6
+ import { cmdRecord, cmdNavigate, cmdNetwork } from './commands/traffic.js';
7
+ import { cmdHar } from './commands/har.js';
8
+
9
+ export async function cdpMain(): Promise<void> {
10
+ const args = process.argv.slice(2);
11
+ const parsed = parseCliArgs(args);
12
+
13
+ switch (parsed.command) {
14
+ case 'detect': return cmdDetect(parsed, args);
15
+ case 'list': return cmdList(parsed, args);
16
+ case 'open': return cmdOpen(parsed, args);
17
+ case 'reset-tab': return cmdResetTab(parsed, args);
18
+ case 'exec': return cmdExec(parsed, args);
19
+ case 'screenshot': return cmdScreenshot(parsed, args);
20
+ case 'click': return cmdClick(parsed, args);
21
+ case 'type': return cmdType(parsed, args);
22
+ case 'a11y': return cmdA11y(parsed, args);
23
+ case 'record': return cmdRecord(parsed, args);
24
+ case 'navigate': return cmdNavigate(parsed, args);
25
+ case 'network': return cmdNetwork(parsed, args);
26
+ case 'har': return cmdHar(parsed, args);
27
+ default:
28
+ console.log(CDP_USAGE);
29
+ }
30
+ }