Skip to main content

browser_automation_cli/commands_prd/
meta.rs

1//! Machine-readable command list and JSON Schema fragments for agents.
2
3use serde_json::{json, Value};
4
5use crate::envelope::print_success_json;
6use crate::error::{CliError, ErrorKind};
7
8/// Full CLI command surface registered for agents (`commands --json`).
9pub const COMMANDS: &[&str] = &[
10    "doctor",
11    "commands",
12    "schema",
13    "version",
14    "goto",
15    "view",
16    "press",
17    "click-at",
18    "write",
19    "keys",
20    "type",
21    "wait",
22    "hover",
23    "drag",
24    "fill-form",
25    "upload",
26    "back",
27    "forward",
28    "reload",
29    "eval",
30    "grab",
31    "run",
32    "exec",
33    "extract",
34    "text",
35    "scroll",
36    "cookie",
37    "attr",
38    "assert",
39    "console",
40    "net",
41    "page",
42    "dialog",
43    "scrape",
44    "batch-scrape",
45    "crawl",
46    "map",
47    "search",
48    "parse",
49    "mitm",
50    "workflow",
51    "config",
52    "emulate",
53    "resize",
54    "perf",
55    "lighthouse",
56    "screencast",
57    "heap",
58    "extension",
59    "devtools3p",
60    "webmcp",
61    "completions",
62];
63
64/// Default-ON DevTools parity commands that MUST appear in `COMMANDS`.
65pub const PARITY_DEFAULT_ON_REQUIRED: &[&str] = &[
66    "goto",
67    "view",
68    "press",
69    "write",
70    "keys",
71    "type",
72    "wait",
73    "hover",
74    "drag",
75    "fill-form",
76    "upload",
77    "back",
78    "forward",
79    "reload",
80    "eval",
81    "grab",
82    "console",
83    "net",
84    "page",
85    "dialog",
86    "emulate",
87    "resize",
88    "perf",
89    "lighthouse",
90    "run",
91    "exec",
92    "doctor",
93    "commands",
94    "schema",
95    "version",
96    "completions",
97];
98
99/// Official DevTools tool-ref name → CLI subcommand (agent discovery).
100pub const DEVTOOLS_TOOL_MAP: &[(&str, &str)] = &[
101    ("click", "press"),
102    ("drag", "drag"),
103    ("fill", "write"),
104    ("fill_form", "fill-form"),
105    ("handle_dialog", "dialog"),
106    ("hover", "hover"),
107    ("press_key", "keys"),
108    ("type_text", "type"),
109    ("upload_file", "upload"),
110    ("click_at", "click-at"),
111    ("navigate_page", "goto|back|forward|reload"),
112    ("new_page", "page new"),
113    ("list_pages", "page list"),
114    ("select_page", "page select"),
115    ("close_page", "page close"),
116    ("wait_for", "wait"),
117    ("emulate", "emulate"),
118    ("resize_page", "resize"),
119    ("performance_start_trace", "perf start"),
120    ("performance_stop_trace", "perf stop"),
121    ("performance_analyze_insight", "perf insight"),
122    ("list_network_requests", "net list"),
123    ("get_network_request", "net get"),
124    ("evaluate_script", "eval"),
125    ("list_console_messages", "console list"),
126    ("get_console_message", "console get"),
127    ("take_screenshot", "grab"),
128    ("take_snapshot", "view"),
129    ("lighthouse_audit", "lighthouse"),
130    ("screencast_start", "screencast start"),
131    ("screencast_stop", "screencast stop"),
132    ("take_heapsnapshot", "heap take"),
133    ("close_heapsnapshot", "heap close"),
134    ("compare_heapsnapshots", "heap compare"),
135    ("get_heapsnapshot_summary", "heap summary"),
136    ("get_heapsnapshot_details", "heap details"),
137    ("get_heapsnapshot_class_nodes", "heap class-nodes"),
138    ("get_heapsnapshot_dominators", "heap dominators"),
139    ("get_heapsnapshot_duplicate_strings", "heap dup-strings"),
140    ("get_heapsnapshot_edges", "heap edges"),
141    ("get_heapsnapshot_retainers", "heap retainers"),
142    ("get_heapsnapshot_retaining_paths", "heap paths"),
143    ("get_heapsnapshot_object_details", "heap object-details"),
144    ("install_extension", "extension install"),
145    ("list_extensions", "extension list"),
146    ("reload_extension", "extension reload"),
147    ("trigger_extension_action", "extension trigger"),
148    ("uninstall_extension", "extension uninstall"),
149    ("list_3p_developer_tools", "devtools3p list"),
150    ("execute_3p_developer_tool", "devtools3p exec"),
151    ("list_webmcp_tools", "webmcp list"),
152    ("execute_webmcp_tool", "webmcp exec"),
153];
154
155pub fn list_commands(json: bool) -> Result<(), CliError> {
156    let map: Vec<Value> = DEVTOOLS_TOOL_MAP
157        .iter()
158        .map(|(tool, cli)| json!({ "tool": tool, "cli": cli }))
159        .collect();
160    let data = json!({
161        "commands": COMMANDS,
162        "schema_version": 1,
163        "parity_default_on": PARITY_DEFAULT_ON_REQUIRED,
164        "devtools_tool_map": map,
165        "binary": "browser-automation-cli",
166    });
167    if json {
168        print_success_json(data)?;
169    } else {
170        for c in COMMANDS {
171            println!("{c}");
172        }
173    }
174    Ok(())
175}
176
177fn schema_object(description: &str, properties: Value, required: &[&str]) -> Value {
178    json!({
179        "type": "object",
180        "description": description,
181        "properties": properties,
182        "required": required,
183        "additionalProperties": false,
184    })
185}
186
187fn schema_for(cmd: &str) -> Option<Value> {
188    let props = match cmd {
189        "doctor" => schema_object(
190            "Diagnose local Chrome install and one-shot readiness",
191            json!({
192                "offline": { "type": "boolean", "description": "Skip network probes" },
193                "quick": { "type": "boolean", "description": "Skip live launch test" },
194                "fix": { "type": "boolean", "description": "Apply safe repairs when possible" },
195                "json": { "type": "boolean" }
196            }),
197            &[],
198        ),
199        "commands" => schema_object(
200            "List available commands",
201            json!({ "json": { "type": "boolean" } }),
202            &[],
203        ),
204        "schema" => schema_object(
205            "JSON Schema fragment for one command",
206            json!({
207                "cmd": { "type": "string", "description": "Command name from `commands`" }
208            }),
209            &["cmd"],
210        ),
211        "version" => schema_object("Print CLI version (JSON when --json)", json!({}), &[]),
212        "goto" => schema_object(
213            "Navigate to URL and wait for load (one-shot)",
214            json!({
215                "url": { "type": "string", "description": "Absolute URL or about:blank" }
216            }),
217            &["url"],
218        ),
219        "view" => schema_object(
220            "Accessibility snapshot with @eN refs",
221            json!({
222                "verbose": { "type": "boolean" },
223                "path": { "type": "string", "description": "Optional file to write tree text" }
224            }),
225            &[],
226        ),
227        "press" => schema_object(
228            "Click element by CSS selector or @eN",
229            json!({
230                "target": { "type": "string" },
231                "dblclick": { "type": "boolean" },
232                "include_snapshot": { "type": "boolean" }
233            }),
234            &["target"],
235        ),
236        "click-at" => schema_object(
237            "Click at page CSS coordinates (requires --experimental-vision)",
238            json!({
239                "x": { "type": "number" },
240                "y": { "type": "number" },
241                "dblclick": { "type": "boolean" },
242                "include_snapshot": { "type": "boolean" }
243            }),
244            &["x", "y"],
245        ),
246        "write" => schema_object(
247            "Smart fill: text, select option, checkbox/radio true|false",
248            json!({
249                "target": { "type": "string" },
250                "value": { "type": "string" },
251                "include_snapshot": { "type": "boolean" }
252            }),
253            &["target", "value"],
254        ),
255        "keys" => schema_object(
256            "Press a CDP key name",
257            json!({ "key": { "type": "string" } }),
258            &["key"],
259        ),
260        "type" => schema_object(
261            "Type text into a target",
262            json!({
263                "target": { "type": "string" },
264                "text": { "type": "string" },
265                "clear": { "type": "boolean" },
266                "submit": { "type": "string", "description": "Optional key after type (e.g. Enter)" }
267            }),
268            &["target", "text"],
269        ),
270        "wait" => schema_object(
271            "Wait ms and/or text and/or CSS selector and/or load state",
272            json!({
273                "ms": { "type": "integer", "minimum": 0 },
274                "text": {
275                    "oneOf": [
276                        { "type": "string" },
277                        { "type": "array", "items": { "type": "string" } }
278                    ],
279                    "description": "Repeatable --text values; any match wins (OR)"
280                },
281                "selector": { "type": "string" },
282                "state": {
283                    "type": "string",
284                    "enum": ["load", "domcontentloaded", "networkidle", "none"]
285                },
286                "wait_timeout_ms": { "type": "integer", "minimum": 0 },
287                "include_snapshot": { "type": "boolean" }
288            }),
289            &[],
290        ),
291        "hover" => schema_object(
292            "Hover element by CSS selector or @eN",
293            json!({ "target": { "type": "string" } }),
294            &["target"],
295        ),
296        "drag" => schema_object(
297            "Drag from one target to another",
298            json!({
299                "from": { "type": "string" },
300                "to": { "type": "string" }
301            }),
302            &["from", "to"],
303        ),
304        "fill-form" => schema_object(
305            "Fill multiple fields from JSON array",
306            json!({
307                "json": {
308                    "type": "string",
309                    "description": "JSON array of {target,value} objects"
310                }
311            }),
312            &["json"],
313        ),
314        "upload" => schema_object(
315            "Upload a regular file to a file input",
316            json!({
317                "target": { "type": "string" },
318                "path": { "type": "string" }
319            }),
320            &["target", "path"],
321        ),
322        "back" => schema_object("History back", json!({}), &[]),
323        "forward" => schema_object("History forward", json!({}), &[]),
324        "reload" => schema_object(
325            "Reload page",
326            json!({ "ignore_cache": { "type": "boolean" } }),
327            &[],
328        ),
329        "eval" => schema_object(
330            "Evaluate JavaScript expression",
331            json!({ "expression": { "type": "string" } }),
332            &["expression"],
333        ),
334        "grab" => schema_object(
335            "Screenshot (png/jpeg/webp)",
336            json!({
337                "path": { "type": "string" },
338                "format": { "type": "string", "enum": ["png", "jpeg", "webp"] },
339                "full_page": { "type": "boolean" },
340                "quality": { "type": "integer" },
341                "element": { "type": "string", "description": "CSS selector or @eN" }
342            }),
343            &[],
344        ),
345        "run" => schema_object(
346            "Execute NDJSON multi-step script in one process",
347            json!({ "script": { "type": "string", "description": "Path to .jsonl script" } }),
348            &["script"],
349        ),
350        "exec" => schema_object(
351            "Single-step inline command (same surface as run steps)",
352            json!({
353                "args": {
354                    "type": "array",
355                    "items": { "type": "string" },
356                    "description": "e.g. [\"goto\", \"about:blank\"] or [\"wait\", \"--ms\", \"100\"]"
357                }
358            }),
359            &["args"],
360        ),
361        "extract" => schema_object(
362            "Extract text or attribute from target",
363            json!({
364                "target": { "type": "string" },
365                "attr": { "type": "string" }
366            }),
367            &["target"],
368        ),
369        "text" => schema_object(
370            "Extract visible text from target (PRD §7)",
371            json!({
372                "target": { "type": "string" }
373            }),
374            &["target"],
375        ),
376        "scroll" => schema_object(
377            "Scroll window or element by pixel deltas",
378            json!({
379                "target": { "type": "string", "description": "Optional CSS/@eN" },
380                "delta_x": { "type": "number" },
381                "delta_y": { "type": "number" }
382            }),
383            &[],
384        ),
385        "cookie" => schema_object(
386            "Cookie list/set/clear for the one-shot browser process",
387            json!({
388                "action": { "type": "string", "enum": ["list", "set", "clear"] },
389                "url": { "type": "string" },
390                "json": { "type": "string", "description": "JSON array for set" }
391            }),
392            &["action"],
393        ),
394        "attr" => schema_object(
395            "Read one attribute from target",
396            json!({
397                "target": { "type": "string" },
398                "name": { "type": "string" }
399            }),
400            &["target", "name"],
401        ),
402        "assert" => schema_object(
403            "Assertion helpers (url/text/console)",
404            json!({
405                "kind": { "type": "string", "enum": ["url", "text", "console"] }
406            }),
407            &["kind"],
408        ),
409        "console" => schema_object(
410            "List/get/clear/dump captured console messages (needs --capture-console)",
411            json!({
412                "action": { "type": "string", "enum": ["list", "get", "clear", "dump"] },
413                "id": { "type": "integer", "minimum": 0 },
414                "path": { "type": "string" }
415            }),
416            &["action"],
417        ),
418        "net" => schema_object(
419            "List or get captured network requests (needs --capture-network)",
420            json!({
421                "action": { "type": "string", "enum": ["list", "get"] },
422                "id": { "type": "integer", "minimum": 0 },
423                "request_path": { "type": "string" },
424                "response_path": { "type": "string" }
425            }),
426            &["action"],
427        ),
428        "page" => schema_object(
429            "Page info or multi-tab list|new|select|close",
430            json!({
431                "action": {
432                    "type": "string",
433                    "enum": ["info", "list", "new", "select", "close"]
434                },
435                "url": { "type": "string" },
436                "index": { "type": "integer", "minimum": 0 }
437            }),
438            &[],
439        ),
440        "dialog" => schema_object(
441            "Accept or dismiss open dialog",
442            json!({
443                "action": { "type": "string", "enum": ["accept", "dismiss"] },
444                "text": { "type": "string" }
445            }),
446            &["action"],
447        ),
448        "scrape" => schema_object(
449            "Navigate and return body text / formats (local Firecrawl-parity)",
450            json!({
451                "url": { "type": "string" },
452                "format": {
453                    "type": "string",
454                    "enum": ["text", "markdown", "html", "links", "metadata"],
455                    "description": "Default text"
456                },
457                "engine": {
458                    "type": "string",
459                    "enum": ["http", "browser"],
460                    "description": "Default browser (CDP)"
461                },
462                "only_main_content": { "type": "boolean" }
463            }),
464            &["url"],
465        ),
466        "batch-scrape" => schema_object(
467            "Scrape many URLs from a file (HTTP engine, one-shot JoinSet)",
468            json!({
469                "urls_file": { "type": "string", "description": "Path to file with one URL per line" },
470                "format": {
471                    "type": "string",
472                    "enum": ["text", "markdown", "html", "links", "metadata"]
473                },
474                "concurrency": { "type": "integer", "minimum": 1 }
475            }),
476            &["urls_file"],
477        ),
478        "crawl" => schema_object(
479            "Crawl from a seed URL (HTTP BFS, one-shot)",
480            json!({
481                "url": { "type": "string" },
482                "limit": { "type": "integer", "minimum": 1 },
483                "max_depth": { "type": "integer", "minimum": 0 },
484                "format": { "type": "string" },
485                "same_host": { "type": "boolean" }
486            }),
487            &["url"],
488        ),
489        "map" => schema_object(
490            "Map site URLs from a seed (HTTP)",
491            json!({
492                "url": { "type": "string" },
493                "limit": { "type": "integer", "minimum": 1 },
494                "max_depth": { "type": "integer", "minimum": 0 }
495            }),
496            &["url"],
497        ),
498        "search" => schema_object(
499            "Local search (HTTP SERP links or URL map)",
500            json!({
501                "query": { "type": "string" },
502                "limit": { "type": "integer", "minimum": 1 }
503            }),
504            &["query"],
505        ),
506        "parse" => schema_object(
507            "Parse a local file (html/md/txt/pdf text extract)",
508            json!({ "path": { "type": "string" } }),
509            &["path"],
510        ),
511        "mitm" => schema_object(
512            "MITM capture / CA / HAR (one-shot local 127.0.0.1)",
513            json!({
514                "action": {
515                    "type": "string",
516                    "enum": [
517                        "status", "list", "get", "har", "export",
518                        "domains", "apis", "init-ca", "start"
519                    ]
520                },
521                "id": { "type": "string" },
522                "out": { "type": "string" },
523                "seconds": { "type": "integer", "minimum": 1 },
524                "limit": { "type": "integer", "minimum": 1 }
525            }),
526            &["action"],
527        ),
528        "workflow" => schema_object(
529            "Workflow journal DAG (petgraph + SQLite under XDG state)",
530            json!({
531                "action": { "type": "string", "enum": ["run", "resume", "status"] },
532                "manifest": { "type": "string", "description": "JSON workflow manifest path" },
533                "journal": { "type": "string" },
534                "name": { "type": "string" }
535            }),
536            &["action"],
537        ),
538        "config" => schema_object(
539            "XDG config and path management (no product env at runtime)",
540            json!({
541                "action": {
542                    "type": "string",
543                    "enum": ["path", "init", "show", "set", "get"]
544                },
545                "key": {
546                    "type": "string",
547                    "description": "For set/get: lang|timeout|artifacts_dir|ignore_robots|namespace|encryption_key|color"
548                },
549                "value": { "type": "string" }
550            }),
551            &["action"],
552        ),
553        "emulate" => schema_object(
554            "Emulate UA locale timezone network geo media CPU viewport headers",
555            json!({
556                "user_agent": { "type": "string" },
557                "locale": { "type": "string" },
558                "timezone": { "type": "string" },
559                "offline": { "type": "boolean" },
560                "latitude": { "type": "number" },
561                "longitude": { "type": "number" },
562                "media": { "type": "string" },
563                "network_conditions": { "type": "string" },
564                "cpu_throttling_rate": { "type": "number" },
565                "color_scheme": { "type": "string" },
566                "extra_headers": { "type": "string" },
567                "viewport": { "type": "string" }
568            }),
569            &[],
570        ),
571        "resize" => schema_object(
572            "Resize viewport",
573            json!({
574                "width": { "type": "integer" },
575                "height": { "type": "integer" },
576                "scale": { "type": "number" },
577                "mobile": { "type": "boolean" }
578            }),
579            &["width", "height"],
580        ),
581        "perf" => schema_object(
582            "Performance start|stop|insight",
583            json!({
584                "action": { "type": "string", "enum": ["start", "stop", "insight"] },
585                "path": { "type": "string" },
586                "reload": { "type": "boolean" },
587                "name": { "type": "string" }
588            }),
589            &["action"],
590        ),
591        "lighthouse" => schema_object(
592            "External lighthouse audit with JSON scores",
593            json!({
594                "url": { "type": "string" },
595                "out_dir": { "type": "string" },
596                "device": { "type": "string" },
597                "mode": { "type": "string" },
598                "lighthouse_path": { "type": "string" }
599            }),
600            &["url"],
601        ),
602        "screencast" => schema_object(
603            "Screencast start|stop (requires --experimental-screencast)",
604            json!({
605                "action": { "type": "string", "enum": ["start", "stop"] },
606                "path": { "type": "string" }
607            }),
608            &["action"],
609        ),
610        "heap" => schema_object(
611            "Heap snapshot tools (deep ops need --category-memory)",
612            json!({
613                "action": { "type": "string" },
614                "path": { "type": "string" },
615                "base": { "type": "string" },
616                "current": { "type": "string" },
617                "id": { "type": "integer" },
618                "node": { "type": "integer" }
619            }),
620            &["action"],
621        ),
622        "extension" => schema_object(
623            "Extension tools (requires --category-extensions)",
624            json!({
625                "action": { "type": "string" },
626                "path": { "type": "string" },
627                "id": { "type": "string" }
628            }),
629            &["action"],
630        ),
631        "devtools3p" => schema_object(
632            "Third-party tools surface (requires --category-third-party)",
633            json!({
634                "action": { "type": "string", "enum": ["list", "exec"] },
635                "name": { "type": "string" },
636                "params": { "type": "string" },
637                "url": { "type": "string" }
638            }),
639            &["action"],
640        ),
641        "webmcp" => schema_object(
642            "Web surface tools (requires --category-webmcp)",
643            json!({
644                "action": { "type": "string", "enum": ["list", "exec"] },
645                "name": { "type": "string" },
646                "input": { "type": "string" },
647                "url": { "type": "string" }
648            }),
649            &["action"],
650        ),
651        "completions" => schema_object(
652            "Generate shell completions (no Chrome)",
653            json!({
654                "shell": {
655                    "type": "string",
656                    "enum": ["bash", "zsh", "fish", "elvish", "powershell"]
657                }
658            }),
659            &["shell"],
660        ),
661        _ => return None,
662    };
663    Some(props)
664}
665
666pub fn schema_for_cmd(cmd: &str, json: bool) -> Result<(), CliError> {
667    if !COMMANDS.contains(&cmd) {
668        return Err(CliError::with_suggestion(
669            ErrorKind::Usage,
670            format!("unknown command for schema: {cmd}"),
671            "use `browser-automation-cli commands --json` to list commands",
672        ));
673    }
674    let fragment = schema_for(cmd)
675        .unwrap_or_else(|| schema_object(&format!("Schema fragment for `{cmd}`"), json!({}), &[]));
676    let data = json!({
677        "command": cmd,
678        "schema_version": 1,
679        "schema": fragment,
680        "type": fragment.get("type").cloned().unwrap_or(json!("object")),
681        "description": fragment.get("description").cloned().unwrap_or(json!("")),
682        "properties": fragment.get("properties").cloned().unwrap_or(json!({})),
683        "required": fragment.get("required").cloned().unwrap_or(json!([])),
684    });
685    if json {
686        print_success_json(data)?;
687    } else {
688        println!(
689            "{}",
690            serde_json::to_string_pretty(&data).unwrap_or_default()
691        );
692    }
693    Ok(())
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699
700    #[test]
701    fn parity_default_on_subset_of_commands() {
702        for req in PARITY_DEFAULT_ON_REQUIRED {
703            assert!(
704                COMMANDS.contains(req),
705                "parity command missing from COMMANDS: {req}"
706            );
707        }
708    }
709
710    #[test]
711    fn commands_unique() {
712        let mut seen = std::collections::BTreeSet::new();
713        for c in COMMANDS {
714            assert!(seen.insert(*c), "duplicate command: {c}");
715        }
716    }
717}