cloud: open machine ports in a browser tab, and list/forward/close ports from the CLI

Merged
TE
11d ago
+1497-15

Audit group H (p0-machines, ergo of commands, docs and errors): open a cloud machine's ports and browser tabs from the CLI and MCP.

Leo: "so chieff related stuff we dont need to worry too much about but ergo of commands and docs and error/hint messages we do need ... anyways those are just quite a few things that we already kinda own/should prob own"
Lawrence: "cloud terminals/browsers too"

What changes

New verbs, all --target machine:<id>, all answering a result to the CLI and MCP (waitsForResult):

VerbWhat it doesResult
cmux cloud open-browser --port N [--path /]Opens the port in a Chromium taburl, surface_id (plus workspace_id on a classic-link machine, or pending: true if the tab hasn't appeared within 15 s)
cmux cloud port-listLists this Mac's forwards of the machine's portsforwards[]
cmux cloud port-forward --port NForwards a port to this Maclocal_port, url, state
cmux cloud port-close --port NCloses a forwardclosed
cmux cloud machine-link [--port N]Returns the machine's link instead of copying ittarget, address, private_url, url, open_url
  • App-server machines (CMUX_NEXT_CLOUD_LINK=app) use cloud.browser.open and cloud.port.list/forward/close. open-browser reuses BrowserTabOpen, the Cloud page's path. Its "Cloud page callers only" check now applies only to the page entry point. A new BrowserTabOpen.request(route:machineName:) builds the request from a route the app asked for itself, with the same proxy and URL checks.
  • Classic-link machines (/api/vm, the default) have no app-server forwards:
    • open-browser opens a CEF tab in the machine's workspace. That workspace's localhost goes to the machine through remote localhost (loopback-forward-v1).
    • The port-* verbs say so and name the command to run instead.
  • Every error names the machine and the port and gives the next command, which is always a CLI-offered verb. A test checks that every hint's verb is a CLI-offered verb.
  • Strings are in new tables (CloudPortActions.xcstrings, CloudPorts.xcstrings) in all 21 languages the l10n gate requires. They don't touch Cloud.xcstrings, which #25217 and the cloud-errors helper are editing.
  • Nested runs. A nested run keeps its outer run's origin, so a script's connect and app ops stay a script's. It changes the view only when the outer run may (ActionRunScope).
  • Answer deadline. A CLI or agent run answers pending at 35 s, inside action.run's 40 s result deadline.
  • Descriptions. Each action has an inline description (#25254), shown in cmux action describe and the MCP tool list.
  • Docs: skills/cmux-cloud-vm/references/commands.md (a new "Ports and browser tabs" section) and web/data/next-docs/pages.ts.

Size

1708 added lines, over the ~800 guide. Most of it is generated or translated data:

  • 525 are the two translation tables (15 keys, 21 languages)
  • 460 are the regenerated action exports (action-surfaces.json, actions.md, cli-reference.generated.json). Base's exports predate its auto-archive actions, so the regenerated files carry those entries too; they are the export script's output for this tree.
  • 215 are tests
  • about 510 are code and docs

The code doesn't split cleanly: the five verbs share one strings table, the failure mapping and the catalog group. Splitting the strings out would leave a PR whose code has no strings.

Capture

No capture: open-browser, port-forward and the other verbs need a live Cloud machine and a Cloud account, which the fleet capture hosts don't have, so a capture would show only a refusal. The visible change is four rows (open-browser and the three port verbs) in a Cloud machine's Connection menu (plans/cmux-next/actions.md, cloudMachine), which appear only for a real machine in the sidebar.

Timers and loops

  • Surface deadline: a one-shot Task.sleep(15 s) in openRoute. It fires once, or it is cancelled in a defer as soon as the tab's surface arrives or the call throws. The stream continuation is finished in the same defer.
  • Answer bound: CloudHandlers.result(pendingAfter: 35 s), the existing helper's one bounded race.
  • No loops and no observers.

Risk

  • New verbs only. The menu rows sit in the machine menu's Connection folder.
  • BrowserTabOpen's page check is unchanged for the Cloud page.
  • pageJSON changes from private to internal.

Tests

CloudBrowserActionTests:

  • the descriptors are CLI verbs and MCP tools
  • a route parses, and a route without its credential or pointing at another host is refused
  • path validation
  • the classic-link refusal for each port op
  • an out-of-range port is refused
  • the port-limit error and other errors
  • every hint is a CLI verb
  • transport and API failures name the machine and port
  • each description names real commands with --target
  • a nested run takes its outer run's origin
  • forward-record fields
  • machine-link without a port

Agent session: Air-Blue

šŸ¤– Generated with Claude Code