Merge pull request #972 from manaflow-ai/task-browser-open-trailing-json-flags
Support trailing browser output flags
This commit is contained in:
commit
43f42b730c
2 changed files with 92 additions and 26 deletions
|
|
@ -2622,7 +2622,34 @@ struct CMUXCLI {
|
|||
throw CLIError(message: "browser requires a subcommand")
|
||||
}
|
||||
|
||||
let (surfaceOpt, argsWithoutSurfaceFlag) = parseOption(commandArgs, name: "--surface")
|
||||
var effectiveJSONOutput = jsonOutput
|
||||
var effectiveIDFormat = idFormat
|
||||
var browserArgs = commandArgs
|
||||
|
||||
// Browser-skill examples often place output flags at the end of the command.
|
||||
// Strip trailing display flags so they don't become part of a URL or selector.
|
||||
while !browserArgs.isEmpty {
|
||||
if browserArgs.last == "--json" {
|
||||
effectiveJSONOutput = true
|
||||
browserArgs.removeLast()
|
||||
continue
|
||||
}
|
||||
|
||||
if browserArgs.count >= 2,
|
||||
browserArgs[browserArgs.count - 2] == "--id-format" {
|
||||
let raw = browserArgs.last!
|
||||
guard let parsed = try CLIIDFormat.parse(raw) else {
|
||||
throw CLIError(message: "--id-format must be one of: refs, uuids, both")
|
||||
}
|
||||
effectiveIDFormat = parsed
|
||||
browserArgs.removeLast(2)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
let (surfaceOpt, argsWithoutSurfaceFlag) = parseOption(browserArgs, name: "--surface")
|
||||
var surfaceRaw = surfaceOpt
|
||||
var args = argsWithoutSurfaceFlag
|
||||
|
||||
|
|
@ -2651,8 +2678,8 @@ struct CMUXCLI {
|
|||
}
|
||||
|
||||
func output(_ payload: [String: Any], fallback: String) {
|
||||
if jsonOutput {
|
||||
print(jsonString(formatIDs(payload, mode: idFormat)))
|
||||
if effectiveJSONOutput {
|
||||
print(jsonString(formatIDs(payload, mode: effectiveIDFormat)))
|
||||
return
|
||||
}
|
||||
print(fallback)
|
||||
|
|
@ -2808,8 +2835,8 @@ struct CMUXCLI {
|
|||
}
|
||||
}
|
||||
let payload = try client.sendV2(method: "browser.open_split", params: params)
|
||||
let surfaceText = formatHandle(payload, kind: "surface", idFormat: idFormat) ?? "unknown"
|
||||
let paneText = formatHandle(payload, kind: "pane", idFormat: idFormat) ?? "unknown"
|
||||
let surfaceText = formatHandle(payload, kind: "surface", idFormat: effectiveIDFormat) ?? "unknown"
|
||||
let paneText = formatHandle(payload, kind: "pane", idFormat: effectiveIDFormat) ?? "unknown"
|
||||
let placement = ((payload["created_split"] as? Bool) == true) ? "split" : "reuse"
|
||||
output(payload, fallback: "OK surface=\(surfaceText) pane=\(paneText) placement=\(placement)")
|
||||
return
|
||||
|
|
@ -2854,8 +2881,8 @@ struct CMUXCLI {
|
|||
if subcommand == "url" || subcommand == "get-url" {
|
||||
let sid = try requireSurface()
|
||||
let payload = try client.sendV2(method: "browser.url.get", params: ["surface_id": sid])
|
||||
if jsonOutput {
|
||||
print(jsonString(formatIDs(payload, mode: idFormat)))
|
||||
if effectiveJSONOutput {
|
||||
print(jsonString(formatIDs(payload, mode: effectiveIDFormat)))
|
||||
} else {
|
||||
print((payload["url"] as? String) ?? "")
|
||||
}
|
||||
|
|
@ -2872,8 +2899,8 @@ struct CMUXCLI {
|
|||
if ["is-webview-focused", "is_webview_focused"].contains(subcommand) {
|
||||
let sid = try requireSurface()
|
||||
let payload = try client.sendV2(method: "browser.is_webview_focused", params: ["surface_id": sid])
|
||||
if jsonOutput {
|
||||
print(jsonString(formatIDs(payload, mode: idFormat)))
|
||||
if effectiveJSONOutput {
|
||||
print(jsonString(formatIDs(payload, mode: effectiveIDFormat)))
|
||||
} else {
|
||||
print((payload["focused"] as? Bool) == true ? "true" : "false")
|
||||
}
|
||||
|
|
@ -2906,8 +2933,8 @@ struct CMUXCLI {
|
|||
}
|
||||
|
||||
let payload = try client.sendV2(method: "browser.snapshot", params: params)
|
||||
if jsonOutput {
|
||||
print(jsonString(formatIDs(payload, mode: idFormat)))
|
||||
if effectiveJSONOutput {
|
||||
print(jsonString(formatIDs(payload, mode: effectiveIDFormat)))
|
||||
} else {
|
||||
print(displaySnapshotText(payload))
|
||||
}
|
||||
|
|
@ -3118,7 +3145,7 @@ struct CMUXCLI {
|
|||
let sid = try requireSurface()
|
||||
let (outPathOpt, _) = parseOption(subArgs, name: "--out")
|
||||
let localJSONOutput = hasFlag(subArgs, name: "--json")
|
||||
let outputAsJSON = jsonOutput || localJSONOutput
|
||||
let outputAsJSON = effectiveJSONOutput || localJSONOutput
|
||||
var payload = try client.sendV2(method: "browser.screenshot", params: ["surface_id": sid])
|
||||
|
||||
func fileURL(fromPath rawPath: String) -> URL {
|
||||
|
|
@ -3233,7 +3260,7 @@ struct CMUXCLI {
|
|||
}
|
||||
|
||||
if outputAsJSON {
|
||||
let formattedPayload = formatIDs(payload, mode: idFormat)
|
||||
let formattedPayload = formatIDs(payload, mode: effectiveIDFormat)
|
||||
if var outputPayload = formattedPayload as? [String: Any] {
|
||||
if hasText(screenshotPath) || hasText(screenshotURL) {
|
||||
outputPayload.removeValue(forKey: "png_base64")
|
||||
|
|
@ -3307,8 +3334,8 @@ struct CMUXCLI {
|
|||
"styles": "browser.get.styles",
|
||||
]
|
||||
let payload = try client.sendV2(method: methodMap[getVerb]!, params: params)
|
||||
if jsonOutput {
|
||||
print(jsonString(formatIDs(payload, mode: idFormat)))
|
||||
if effectiveJSONOutput {
|
||||
print(jsonString(formatIDs(payload, mode: effectiveIDFormat)))
|
||||
} else if let value = payload["value"] {
|
||||
if let str = value as? String {
|
||||
print(str)
|
||||
|
|
@ -3347,8 +3374,8 @@ struct CMUXCLI {
|
|||
throw CLIError(message: "Unsupported browser is subcommand: \(isVerb)")
|
||||
}
|
||||
let payload = try client.sendV2(method: method, params: ["surface_id": sid, "selector": selector])
|
||||
if jsonOutput {
|
||||
print(jsonString(formatIDs(payload, mode: idFormat)))
|
||||
if effectiveJSONOutput {
|
||||
print(jsonString(formatIDs(payload, mode: effectiveIDFormat)))
|
||||
} else if let value = payload["value"] {
|
||||
print("\(value)")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -91,6 +91,32 @@ def _run_cli_text(cli: str, args: list[str], retries: int = 3) -> str:
|
|||
|
||||
raise cmuxError(f"CLI failed ({' '.join(args)}): {last_merged}")
|
||||
|
||||
|
||||
def _run_cli_tail_json(cli: str, args: list[str], retries: int = 3) -> dict:
|
||||
last_merged = ""
|
||||
for attempt in range(1, retries + 1):
|
||||
proc = subprocess.run(
|
||||
[cli, "--socket", SOCKET_PATH] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if proc.returncode == 0:
|
||||
try:
|
||||
return json.loads(proc.stdout or "{}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise cmuxError(f"Invalid CLI JSON output for {' '.join(args)}: {proc.stdout!r} ({exc})")
|
||||
|
||||
merged = f"{proc.stdout}\n{proc.stderr}".strip()
|
||||
last_merged = merged
|
||||
if "Command timed out" in merged and attempt < retries:
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
raise cmuxError(f"CLI failed ({' '.join(args)}): {merged}")
|
||||
|
||||
raise cmuxError(f"CLI failed ({' '.join(args)}): {last_merged}")
|
||||
|
||||
|
||||
def _run_cli_expect_failure(cli: str, args: list[str], needles: list[str]) -> None:
|
||||
proc = subprocess.run(
|
||||
[cli, "--socket", SOCKET_PATH, "--json"] + args,
|
||||
|
|
@ -144,15 +170,6 @@ def main() -> int:
|
|||
cli = _find_cli_binary()
|
||||
|
||||
with _local_test_server() as page_url:
|
||||
opened = _run_cli_json(cli, ["browser", "open", page_url])
|
||||
surface = str(opened.get("surface_ref") or opened.get("surface_id") or "")
|
||||
_must(bool(surface), f"browser open returned no surface handle: {opened}")
|
||||
_must(surface.startswith("surface:"), f"Expected short surface ref from browser open, got: {opened}")
|
||||
|
||||
_run_cli_json(cli, ["browser", surface, "wait", "--load-state", "complete", "--timeout-ms", "15000"])
|
||||
snapshot_text = _run_cli_text(cli, ["browser", surface, "snapshot", "--interactive"])
|
||||
_must("ref=e" in snapshot_text, f"Expected snapshot text with refs from CLI: {snapshot_text!r}")
|
||||
|
||||
identify = _run_cli_json(cli, ["identify"])
|
||||
focused = identify.get("focused") or {}
|
||||
workspace = str(
|
||||
|
|
@ -163,6 +180,28 @@ def main() -> int:
|
|||
or ""
|
||||
)
|
||||
_must(bool(workspace), f"Expected workspace handle from identify: {identify}")
|
||||
os.environ["CMUX_WORKSPACE_ID"] = workspace
|
||||
|
||||
opened_tail_json = _run_cli_tail_json(
|
||||
cli,
|
||||
["browser", "open", page_url, "--workspace", workspace, "--id-format", "both", "--json"],
|
||||
)
|
||||
tail_surface = str(opened_tail_json.get("surface_ref") or "")
|
||||
_must(tail_surface.startswith("surface:"), f"Expected trailing --json browser open to return surface_ref: {opened_tail_json}")
|
||||
_must(bool(opened_tail_json.get("surface_id")), f"Expected trailing --id-format both to preserve surface_id: {opened_tail_json}")
|
||||
_must("--json" not in str(opened_tail_json.get("url") or ""), f"Trailing output flags leaked into browser open URL: {opened_tail_json}")
|
||||
_run_cli_json(cli, ["browser", tail_surface, "wait", "--load-state", "complete", "--timeout-ms", "15000"])
|
||||
tail_url_payload = _run_cli_json(cli, ["browser", tail_surface, "url"])
|
||||
_must(str(tail_url_payload.get("url") or "").startswith(page_url), f"Expected trailing --json browser open to navigate: {tail_url_payload}")
|
||||
|
||||
opened = _run_cli_json(cli, ["browser", "open", page_url])
|
||||
surface = str(opened.get("surface_ref") or opened.get("surface_id") or "")
|
||||
_must(bool(surface), f"browser open returned no surface handle: {opened}")
|
||||
_must(surface.startswith("surface:"), f"Expected short surface ref from browser open, got: {opened}")
|
||||
|
||||
_run_cli_json(cli, ["browser", surface, "wait", "--load-state", "complete", "--timeout-ms", "15000"])
|
||||
snapshot_text = _run_cli_text(cli, ["browser", surface, "snapshot", "--interactive"])
|
||||
_must("ref=e" in snapshot_text, f"Expected snapshot text with refs from CLI: {snapshot_text!r}")
|
||||
|
||||
blank_opened = _run_cli_json(cli, ["browser", "open", "about:blank", "--workspace", workspace])
|
||||
blank_surface = str(blank_opened.get("surface_ref") or blank_opened.get("surface_id") or "")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue