Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/browser/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ class CDPPage extends BasePage {
}> = [];
private _pendingRequests = new Map<string, number>(); // requestId → index in _networkEntries
private _pendingBodyFetches: Set<Promise<void>> = new Set(); // track in-flight getResponseBody calls
private _consoleMessages: Array<{ type: string; text: string; timestamp: number }> = [];
private _consoleCapturing = false;

constructor(private bridge: CDPBridge) {
super();
Expand Down Expand Up @@ -298,6 +300,30 @@ class CDPPage extends BasePage {
return entries;
}

async consoleMessages(level: string = 'all'): Promise<Array<{ type: string; text: string; timestamp: number }>> {
if (!this._consoleCapturing) {
await this.bridge.send('Runtime.enable');
this.bridge.on('Runtime.consoleAPICalled', (params: unknown) => {
const p = params as { type: string; args: Array<{ value?: unknown; description?: string }>; timestamp: number };
const text = (p.args || []).map(a => a.value !== undefined ? String(a.value) : (a.description || '')).join(' ');
this._consoleMessages.push({ type: p.type, text, timestamp: p.timestamp });
if (this._consoleMessages.length > 500) this._consoleMessages.shift();
});
// Capture uncaught exceptions as error-level messages
this.bridge.on('Runtime.exceptionThrown', (params: unknown) => {
const p = params as { timestamp: number; exceptionDetails?: { exception?: { description?: string }; text?: string } };
const desc = p.exceptionDetails?.exception?.description || p.exceptionDetails?.text || 'Unknown exception';
this._consoleMessages.push({ type: 'error', text: desc, timestamp: p.timestamp });
if (this._consoleMessages.length > 500) this._consoleMessages.shift();
});
this._consoleCapturing = true;
}
if (level === 'all') return [...this._consoleMessages];
// 'error' level includes both console.error() and uncaught exceptions
if (level === 'error') return this._consoleMessages.filter(m => m.type === 'error' || m.type === 'warning');
return this._consoleMessages.filter(m => m.type === level);
}

async tabs(): Promise<unknown[]> {
return [];
}
Expand Down
42 changes: 34 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,14 @@ export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string): Command

operate.command('open').argument('<url>').description('Open URL in automation window')
.action(operateAction(async (page, url) => {
// Start session-level capture before navigation (catches initial requests)
await page.startNetworkCapture?.();
await page.goto(url);
await page.wait(2);
// Auto-inject network interceptor for API discovery
try { await page.evaluate(NETWORK_INTERCEPTOR_JS); } catch { /* non-fatal */ }
// Fallback: also inject JS interceptor for pages without session capture
if (!page.startNetworkCapture) {
try { await page.evaluate(NETWORK_INTERCEPTOR_JS); } catch { /* non-fatal */ }
}
console.log(`Navigated to: ${await page.getCurrentUrl?.() ?? url}`);
}));

Expand Down Expand Up @@ -505,13 +509,35 @@ export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string): Command
.option('--all', 'Show all requests including static resources')
.description('Show captured network requests (auto-captured since last open)')
.action(operateAction(async (page, opts) => {
const requests = await page.evaluate(`(function(){
var reqs = window.__opencli_net || [];
return JSON.stringify(reqs);
})()`) as string;

let items: Array<{ url: string; method: string; status: number; size: number; ct: string; body: unknown }> = [];
try { items = JSON.parse(requests); } catch { console.log('No network data captured. Run "operate open <url>" first.'); return; }
if (page.readNetworkCapture) {
const raw = await page.readNetworkCapture();
// Normalize daemon/CDP capture entries to __opencli_net shape.
// Daemon returns: responseStatus, responseContentType, responsePreview
// CDP returns the same shape after PR A fix.
items = (raw as Array<Record<string, unknown>>).map(e => {
const preview = (e.responsePreview as string) ?? null;
let body: unknown = null;
if (preview) {
try { body = JSON.parse(preview); } catch { body = preview; }
}
return {
url: (e.url as string) || '',
method: (e.method as string) || 'GET',
status: (e.responseStatus as number) || 0,
size: preview ? preview.length : 0,
ct: (e.responseContentType as string) || '',
body,
};
});
} else {
// Fallback to JS interceptor data
const requests = await page.evaluate(`(function(){
var reqs = window.__opencli_net || [];
return JSON.stringify(reqs);
})()`) as string;
try { items = JSON.parse(requests); } catch { console.log('No network data captured. Run "operate open <url>" first.'); return; }
}

if (items.length === 0) { console.log('No requests captured.'); return; }

Expand Down
Loading