cdp-commands.ts
64 lines · browse/src/cdp-commands.ts at 3f79d81
1/**
2 * $B cdp <Domain.method> [json-params] — CLI surface for the CDP escape hatch.
3 *
4 * Output for trusted methods is a plain JSON pretty-print.
5 * Output for untrusted methods is wrapped with the centralized UNTRUSTED EXTERNAL
6 * CONTENT envelope so downstream consumers treat it as data, not instructions
7 * (matches the pattern used by other untrusted-content commands in commands.ts).
8 */
10import type { BrowserManager } from './browser-manager';
11import { dispatchCdpCall } from './cdp-bridge';
12import { wrapUntrustedContent } from './commands';
14function parseQualified(name: string): { domain: string; method: string } {
15 const idx = name.indexOf('.');
16 if (idx <= 0 || idx === name.length - 1) {
17 throw new Error(
18 `Usage: $B cdp <Domain.method> [json-params]\n` +
19 `Cause: '${name}' is not in Domain.method format.\n` +
20 'Action: e.g. $B cdp Accessibility.getFullAXTree {}'
21 );
22 }
23 return { domain: name.slice(0, idx), method: name.slice(idx + 1) };
24}
26export async function handleCdpCommand(args: string[], bm: BrowserManager): Promise<string> {
27 if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {
28 return [
29 '$B cdp — raw CDP method dispatch (deny-default escape hatch)',
30 '',
31 'Usage: $B cdp <Domain.method> [json-params]',
32 '',
33 'Allowed methods are listed in browse/src/cdp-allowlist.ts. To add one,',
34 'open a PR with a one-line justification and the (scope, output) tags.',
35 'Examples:',
36 ' $B cdp Accessibility.getFullAXTree {}',
37 ' $B cdp Performance.getMetrics {}',
38 ' $B cdp DOM.describeNode \'{"backendNodeId":42,"depth":3}\'',
39 ].join('\n');
40 }
41 const qualified = args[0]!;
42 const { domain, method } = parseQualified(qualified);
43 // Optional second arg is JSON params; default to {}.
44 let params: Record<string, unknown> = {};
45 if (args[1]) {
46 try {
47 params = JSON.parse(args[1]) ?? {};
48 } catch (e: any) {
49 throw new Error(
50 `Cannot parse params as JSON: ${e.message}\n` +
51 `Cause: argument '${args[1]}' is not valid JSON.\n` +
52 'Action: pass a JSON object literal, e.g. \'{"backendNodeId":42}\'.'
53 );
54 }
55 }
56 // Dispatch via the bridge (allowlist + mutex + timeout + finally-release).
57 const tabId = bm.getActiveTabId();
58 const { raw, entry } = await dispatchCdpCall({ domain, method, params, tabId, bm });
59 const json = JSON.stringify(raw, null, 2);
60 if (entry.output === 'untrusted') {
61 return wrapUntrustedContent(json, `cdp:${qualified}`);
62 }
63 return json;
64}