Repository navigation
Add cmux vm command aliases #6375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
922394a
0447a31
6052c56
9314041
1bbb37e
b9fbbe4
f5da007
15d375d
5b71a4f
88945c8
d815ff6
68edbe2
f2b8723
14e146f
2066c07
fc963ce
19ce61d
1056f32
3822baf
83bfdf9
9e07507
76cda4a
bb824e5
31ead62
83a585c
4858b68
121aa30
bf03a63
ec88fed
f727997
f231c08
f148fc2
239cf23
985dcdc
846c732
48d1f7a
58390fe
52b9ca9
decdf48
400b43a
3313655
c5cb262
1bc6d9a
1c0ca12
e9a19e9
cca1de8
86b9aad
4c73412
47a0131
90244cb
415e56d
66f69fa
e436e63
00d1ae6
0dead9c
56e67cc
5868fce
a9ca449
90ca2ad
a2a365c
f35747e
01ec688
8ba7cca
211f9cd
40ce8f9
cdcea70
567ea0e
19e5fa6
a5b91d2
ff8f866
1f5bf18
be7a72e
df776cb
9d554de
bd056d4
484c980
42631af
cadea32
7d57661
38dcda7
ebd50b4
7bb15c6
4882b9e
714c306
b3daf57
d7ac49c
5d3616c
739fc83
72a77bd
d5678f3
4cb176a
64270ee
6b82924
7827038
2477680
d47280c
4ec20ab
ac343dc
844a508
55273be
8a21abf
01cfdec
0ad24aa
81e4e0a
6b6fc99
8c53ab5
78f4c82
57b0073
5994f5e
d68ae17
4b00ea5
f174e18
0c7ccb4
8292cbe
64a665a
df3ea68
956104e
c5a666a
aed23e6
72e2daa
5228f1f
fd5d49d
5395d85
e57f6de
872f46e
89c573a
a50e75f
ae639d3
f16bdc0
7713752
1e75269
fe8e7af
a5ab2bf
cdb73a1
efb20d4
c082946
1b332f7
7898723
ddf32d3
14d9ccf
83f62ff
5b8eca4
e07ed87
9b3b242
99efdeb
dcb60aa
9d0cb80
89d02bd
b99c964
03134f3
cbda7d4
2f36e55
4ca686d
b1ee541
c08bcd3
5373b4a
9fe20d7
ec442c4
8051e48
68df8ee
e604d8d
ec4cbd4
4ac2570
860aa16
3f669c7
9a916eb
be7d9fa
65033c2
e2545e1
dad840d
dc6c414
52fa496
78c15bf
fa424e6
3ea0ec5
bb2f674
bbe64f6
abc88f2
6efd462
3df8e4e
6dc1794
f39c6ba
8986181
3283ee9
c765ac2
3773d55
41b3540
60668a9
27131a2
1a58732
a056a2d
a8989a2
f5ad88a
18c628a
7247ee6
cf97ee6
8052214
9c5b6a6
40ad414
90265e6
c11cad5
178e325
a4cf484
a15556a
3bdfaaa
53eb96e
a40278b
380db00
8c43c63
7ef0f7a
228a876
34dc675
519b5b5
69f5b5d
4e73d97
c06913a
8bcf5b2
c01e9b5
a149b7e
8008b7c
97c6088
5c30554
f6a67df
88ad621
0633852
7037793
e58fbe7
ebe4f61
3bab274
accdec4
5f94b9c
48d73a0
7bda7ff
e58e599
993d91a
f9178e0
8d7a3c2
7907779
e9a53ed
d9d87d3
c56d008
8083442
b685a27
8aaad8c
9bf6cb8
3a277f8
dbf7633
5f8dd51
7e0da6b
01faafe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3992,11 +3992,11 @@ struct CMUXCLI { | |
| ) | ||
| Self.clearVMCreateIdempotency(idempotency) | ||
|
|
||
| case "shell", "attach": | ||
| case "shell", "attach", "connect": | ||
| let (windowOpt, vmArgs) = parseOption(rest, name: "--window") | ||
| guard let vmId = vmArgs.first else { | ||
| throw CLIError(message: """ | ||
| Usage: cmux \(command) shell <id> | ||
| Usage: cmux vm ssh <id> | ||
|
|
||
| Find an id: | ||
| cmux vm ls | ||
|
|
@@ -4012,7 +4012,7 @@ struct CMUXCLI { | |
| idFormat: idFormat | ||
| ) | ||
|
|
||
| case "rm", "destroy", "delete": | ||
| case "rm", "remove", "destroy", "delete": | ||
| guard let vmId = rest.first else { | ||
| throw CLIError(message: """ | ||
| Usage: cmux vm rm <id> | ||
|
|
@@ -4118,13 +4118,19 @@ struct CMUXCLI { | |
|
|
||
| default: | ||
| throw CLIError(message: """ | ||
| Usage: cmux \(command) <ls|new|shell|rm|exec|ssh> [args...] | ||
| Usage: cmux \(command) <new|ls|rm|exec|ssh|ssh-info> [args...] | ||
|
|
||
| Common commands: | ||
| cmux vm ls | ||
| cmux vm new | ||
| cmux vm ssh <id> | ||
| cmux vm rm <id> | ||
|
|
||
| Aliases are available for familiar spelling: | ||
| create -> new | ||
| list -> ls | ||
| shell, attach, connect -> ssh | ||
| remove, delete, destroy -> rm | ||
| """) | ||
| } | ||
|
|
||
|
|
@@ -14436,7 +14442,7 @@ struct CMUXCLI { | |
| """ | ||
| case "vm", "cloud": | ||
| return """ | ||
| Usage: cmux \(command) <new|ls|rm|exec|shell|attach|ssh|ssh-info> [args...] | ||
| Usage: cmux \(command) <new|ls|rm|exec|ssh|ssh-info> [args...] | ||
|
|
||
| Manage cloud VMs. `cloud` is an alias for `vm`. Requires `cmux auth login`. | ||
|
|
||
|
|
@@ -14446,9 +14452,6 @@ struct CMUXCLI { | |
| Create a new VM. By default drops you into a shell on | ||
| the VM (like `docker run -it`). Pass --detach/-d to | ||
| just print the id and exit (scripting primitive). | ||
| shell <id> [--window <id|ref|index>] | ||
| Drop into an interactive shell on an existing VM. | ||
| Alias: `attach <id>`. | ||
| ssh <id> [--window <id|ref|index>] | ||
| Drop into a cmux-managed SSH workspace for an existing | ||
| VM, using the same session path as `cmux ssh`. | ||
|
|
@@ -14457,6 +14460,12 @@ struct CMUXCLI { | |
| rm <id> Destroy a VM. | ||
| exec <id> -- <command...> Run a shell command inside the VM and print stdout. | ||
|
|
||
| Aliases: | ||
| create Alias for `new`; prefer `new` in docs and scripts. | ||
| list Alias for `ls`; prefer `ls` in docs and scripts. | ||
| shell, attach, connect Aliases for `ssh`; prefer `ssh` in docs and scripts. | ||
| remove, delete, destroy Aliases for `rm`; prefer `rm` in docs and scripts. | ||
|
|
||
| Env: | ||
| CMUX_VM_API_BASE_URL Override the backend origin (default: the cmux website). | ||
| `bun run dev` derives this from CMUX_PORT/PORT for | ||
|
|
@@ -34262,7 +34271,7 @@ export default function cmuxPiSessionExtension(pi: ExtensionAPI) { | |
| events [--after <seq>] [--cursor-file <path>] [--name <event>] [--category <category>] [--reconnect] [--limit <n>] [--no-ack] [--no-heartbeat] | ||
| auth <status|login|logout> | ||
| login | logout (aliases for auth login/logout) | ||
| vm <new|ls|rm|exec|shell|ssh> [args...] (alias: cloud) | ||
| vm <new|ls|rm|exec|ssh|ssh-info> [args...] (alias: cloud; accepts create/list/attach/remove aliases) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incomplete alias list in global command documentation. The parenthetical note says "accepts create/list/attach/remove aliases", but this omits several aliases that are documented in the detailed help (lines 14463-14468) and the default error message (lines 4129-4133):
This creates inconsistency between the comprehensive alias list in the detailed help and this brief global summary. 📝 Proposed fix for completeness- vm <new|ls|rm|exec|ssh|ssh-info> [args...] (alias: cloud; accepts create/list/attach/remove aliases)
+ vm <new|ls|rm|exec|ssh|ssh-info> [args...] (alias: cloud; accepts create/list/shell/attach/connect/remove/delete/destroy aliases)Or for brevity while staying accurate: - vm <new|ls|rm|exec|ssh|ssh-info> [args...] (alias: cloud; accepts create/list/attach/remove aliases)
+ vm <new|ls|rm|exec|ssh|ssh-info> [args...] (alias: cloud; see `cmux vm` for full alias list)🤖 Prompt for AI Agents |
||
| remotes <list|add|remove> [--route <host:port>] [--tag <tag>] [--json] (alias: remote) | ||
| rpc <method> [json-params] | ||
| identify [--workspace <id|ref|index>] [--surface <id|ref|index>] [--window <id|ref|index>] [--no-caller] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,211 @@ import XCTest | |
| import Darwin | ||
|
|
||
| extension CLINotifyProcessIntegrationRegressionTests { | ||
| func testVMCreateAliasDispatchesThroughVMCreate() throws { | ||
| let cliPath = try bundledCLIPath() | ||
| let socketPath = makeSocketPath("vm-create-alias") | ||
| let listenerFD = try bindUnixSocket(at: socketPath) | ||
| let state = MockSocketServerState() | ||
| let tempHome = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) | ||
| .appendingPathComponent("cmux-vm-create-alias-\(UUID().uuidString)", isDirectory: true) | ||
| try FileManager.default.createDirectory(at: tempHome, withIntermediateDirectories: true) | ||
|
|
||
| defer { | ||
| Darwin.close(listenerFD) | ||
| unlink(socketPath) | ||
| try? FileManager.default.removeItem(at: tempHome) | ||
| } | ||
|
|
||
| let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in | ||
| guard let payload = self.jsonObject(line), | ||
| let id = payload["id"] as? String, | ||
| let method = payload["method"] as? String else { | ||
| return self.malformedRequestResponse(raw: line) | ||
| } | ||
| guard method == "vm.create" else { | ||
| return self.v2Response(id: id, ok: false, error: ["code": "unexpected", "message": "Unexpected method \(method)"]) | ||
| } | ||
| let params = payload["params"] as? [String: Any] ?? [:] | ||
| XCTAssertEqual(params["provider"] as? String, "freestyle") | ||
| XCTAssertNotNil(params["idempotency_key"] as? String) | ||
| return self.v2Response( | ||
| id: id, | ||
| ok: true, | ||
| result: [ | ||
| "id": "vm-create-alias", | ||
| "provider": "freestyle", | ||
| "image": "default", | ||
| ] | ||
| ) | ||
| } | ||
|
|
||
| var environment = ProcessInfo.processInfo.environment | ||
| environment["CMUX_SOCKET_PATH"] = socketPath | ||
| environment["CMUX_CLI_SENTRY_DISABLED"] = "1" | ||
| environment["CMUX_CLAUDE_HOOK_SENTRY_DISABLED"] = "1" | ||
| environment["CFFIXED_USER_HOME"] = tempHome.path | ||
|
|
||
| let result = runProcess( | ||
| executablePath: cliPath, | ||
| arguments: ["vm", "create", "--provider", "freestyle", "--detach"], | ||
| environment: environment, | ||
| timeout: 5 | ||
| ) | ||
|
|
||
| wait(for: [serverHandled], timeout: 5) | ||
| XCTAssertFalse(result.timedOut, result.stderr) | ||
| XCTAssertEqual(result.status, 0, result.stderr) | ||
| XCTAssertTrue(result.stdout.contains("OK vm-create-alias"), result.stdout) | ||
| XCTAssertEqual( | ||
| state.commands.compactMap { self.jsonObject($0)?["method"] as? String }, | ||
| ["vm.create"] | ||
| ) | ||
| } | ||
|
|
||
| func testVMRemoveAliasDispatchesThroughVMDestroy() throws { | ||
| let cliPath = try bundledCLIPath() | ||
| let socketPath = makeSocketPath("vm-remove-alias") | ||
| let listenerFD = try bindUnixSocket(at: socketPath) | ||
| let state = MockSocketServerState() | ||
| let vmID = "vm-remove-alias" | ||
|
|
||
| defer { | ||
| Darwin.close(listenerFD) | ||
| unlink(socketPath) | ||
| } | ||
|
|
||
| let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in | ||
| guard let payload = self.jsonObject(line), | ||
| let id = payload["id"] as? String, | ||
| let method = payload["method"] as? String else { | ||
| return self.malformedRequestResponse(raw: line) | ||
| } | ||
| guard method == "vm.destroy" else { | ||
| return self.v2Response(id: id, ok: false, error: ["code": "unexpected", "message": "Unexpected method \(method)"]) | ||
| } | ||
| let params = payload["params"] as? [String: Any] ?? [:] | ||
| XCTAssertEqual(params["id"] as? String, vmID) | ||
| return self.v2Response(id: id, ok: true, result: ["ok": true]) | ||
| } | ||
|
|
||
| var environment = ProcessInfo.processInfo.environment | ||
| environment["CMUX_SOCKET_PATH"] = socketPath | ||
| environment["CMUX_CLI_SENTRY_DISABLED"] = "1" | ||
| environment["CMUX_CLAUDE_HOOK_SENTRY_DISABLED"] = "1" | ||
|
|
||
| let result = runProcess( | ||
| executablePath: cliPath, | ||
| arguments: ["vm", "remove", vmID], | ||
| environment: environment, | ||
| timeout: 5 | ||
| ) | ||
|
|
||
| wait(for: [serverHandled], timeout: 5) | ||
| XCTAssertFalse(result.timedOut, result.stderr) | ||
| XCTAssertEqual(result.status, 0, result.stderr) | ||
| XCTAssertEqual(result.stdout, "OK \(vmID)\n") | ||
| XCTAssertEqual( | ||
| state.commands.compactMap { self.jsonObject($0)?["method"] as? String }, | ||
| ["vm.destroy"] | ||
| ) | ||
| } | ||
|
|
||
| func testVMConnectAliasOpensManagedWorkspaceThroughSharedSSHPath() throws { | ||
| let cliPath = try bundledCLIPath() | ||
| let socketPath = makeSocketPath("vm-connect") | ||
| let listenerFD = try bindUnixSocket(at: socketPath) | ||
| let state = MockSocketServerState() | ||
| let vmID = "vm-connect-1234567890" | ||
| let workspaceID = "11111111-1111-1111-1111-111111111111" | ||
| let workspaceRef = "workspace:connect" | ||
|
|
||
| defer { | ||
| Darwin.close(listenerFD) | ||
| unlink(socketPath) | ||
| } | ||
|
|
||
| let serverHandled = startMockServer(listenerFD: listenerFD, state: state) { line in | ||
| guard let payload = self.jsonObject(line), | ||
| let id = payload["id"] as? String, | ||
| let method = payload["method"] as? String else { | ||
| return self.malformedRequestResponse(raw: line) | ||
| } | ||
|
|
||
| switch method { | ||
| case "vm.attach_info": | ||
| let params = payload["params"] as? [String: Any] ?? [:] | ||
| XCTAssertEqual(params["id"] as? String, vmID) | ||
| XCTAssertEqual(params["require_daemon"] as? Bool, true) | ||
| return self.v2Response( | ||
| id: id, | ||
| ok: true, | ||
| result: [ | ||
| "transport": "ssh", | ||
| "host": "gateway.freestyle.sh", | ||
| "port": 2222, | ||
| "username": "cmux", | ||
| "credential": [ | ||
| "kind": "password", | ||
| "value": "lease-token", | ||
| ], | ||
| ] | ||
| ) | ||
| case "workspace.create": | ||
| return self.v2Response( | ||
| id: id, | ||
| ok: true, | ||
| result: [ | ||
| "workspace_id": workspaceID, | ||
| ] | ||
| ) | ||
| case "workspace.rename": | ||
| return self.v2Response(id: id, ok: true, result: ["workspace_id": workspaceID]) | ||
| case "workspace.remote.configure": | ||
| return self.v2Response( | ||
| id: id, | ||
| ok: true, | ||
| result: [ | ||
| "workspace_id": workspaceID, | ||
| "workspace_ref": workspaceRef, | ||
| "remote": [ | ||
| "enabled": true, | ||
| "state": "connecting", | ||
| ], | ||
| ] | ||
| ) | ||
| case "workspace.select": | ||
| return self.v2Response(id: id, ok: true, result: ["workspace_id": workspaceID]) | ||
| default: | ||
| return self.v2Response( | ||
| id: id, | ||
| ok: false, | ||
| error: ["code": "unexpected", "message": "Unexpected method \(method)"] | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| var environment = ProcessInfo.processInfo.environment | ||
| environment["CMUX_SOCKET_PATH"] = socketPath | ||
| environment["CMUX_CLI_SENTRY_DISABLED"] = "1" | ||
| environment["CMUX_CLAUDE_HOOK_SENTRY_DISABLED"] = "1" | ||
|
|
||
| let result = runProcess( | ||
| executablePath: cliPath, | ||
| arguments: ["vm", "connect", vmID], | ||
| environment: environment, | ||
| timeout: 5 | ||
| ) | ||
|
|
||
| wait(for: [serverHandled], timeout: 5) | ||
| XCTAssertFalse(result.timedOut, result.stderr) | ||
| XCTAssertEqual(result.status, 0, result.stderr) | ||
| XCTAssertEqual(result.stdout, "OK workspace=\(workspaceRef) target=cmux@gateway.freestyle.sh state=connecting\n") | ||
| XCTAssertEqual( | ||
| state.commands.compactMap { self.jsonObject($0)?["method"] as? String }, | ||
| ["vm.attach_info", "workspace.create", "workspace.rename", "workspace.remote.configure", "workspace.select"] | ||
| ) | ||
| } | ||
|
Comment on lines
+143
to
+207
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| func testVMSSHOpensManagedWorkspaceThroughSharedSSHPath() throws { | ||
| let cliPath = try bundledCLIPath() | ||
| let socketPath = makeSocketPath("vm-ssh") | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.