cloud: open machine ports in a browser tab, and list/forward/close ports from the CLI
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):
| Verb | What it does | Result |
|---|---|---|
cmux cloud open-browser --port N [--path /] | Opens the port in a Chromium tab | url, 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-list | Lists this Mac's forwards of the machine's ports | forwards[] |
cmux cloud port-forward --port N | Forwards a port to this Mac | local_port, url, state |
cmux cloud port-close --port N | Closes a forward | closed |
cmux cloud machine-link [--port N] | Returns the machine's link instead of copying it | target, address, private_url, url, open_url |
- App-server machines (
CMUX_NEXT_CLOUD_LINK=app) usecloud.browser.openandcloud.port.list/forward/close. open-browser reusesBrowserTabOpen, the Cloud page's path. Its "Cloud page callers only" check now applies only to the page entry point. A newBrowserTabOpen.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-browseropens a CEF tab in the machine's workspace. That workspace'slocalhostgoes 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 touchCloud.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
pendingat 35 s, insideaction.run's 40 s result deadline. - Descriptions. Each action has an inline
description(#25254), shown incmux action describeand the MCP tool list. - Docs:
skills/cmux-cloud-vm/references/commands.md(a new "Ports and browser tabs" section) andweb/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)inopenRoute. It fires once, or it is cancelled in adeferas soon as the tab's surface arrives or the call throws. The stream continuation is finished in the samedefer. - 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.pageJSONchanges 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