@rigkit/provider-cmux 0.2.13 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,67 +1,52 @@
1
1
  # @rigkit/provider-cmux
2
2
 
3
- Small SDK and Rigkit provider facade for opening workspaces in local `cmux`.
3
+ Small SDK and Rigkit provider facade for driving local `cmux`.
4
4
 
5
5
  ```ts
6
6
  import { createCmuxClient } from "@rigkit/provider-cmux";
7
7
 
8
8
  const cmux = createCmuxClient();
9
9
 
10
- await cmux.newWorkspace({
10
+ const workspace = await cmux.newWorkspace({
11
11
  name: "playground",
12
- command: "echo hello world",
12
+ focus: true,
13
+ });
14
+ await cmux.newSurface({
15
+ workspace: workspace.id ?? workspace.handle,
16
+ type: "terminal",
13
17
  focus: true,
14
18
  });
15
19
  ```
16
20
 
17
- Commands are printed to stderr before execution:
18
-
19
- ```text
20
- $ cmux new-workspace --name playground --command 'echo hello world' --focus true
21
- ```
22
-
23
- `cmux new-workspace` and `cmux ssh` are socket commands. With cmux's default socket control mode (`cmuxOnly`), run rigkit from a terminal inside cmux so cmux sets `CMUX_SOCKET_PATH` and accepts the process.
24
-
25
- If you intentionally enable external socket control in cmux, opt in explicitly:
21
+ Workflow operations use raw cmux actions through the provider facade:
26
22
 
27
23
  ```ts
28
- const cmux = createCmuxClient({ allowExternalAutomation: true });
29
- ```
24
+ const workspace = await providers.cmux.ssh({
25
+ destination: "root@devbox.example.com",
26
+ name: "site",
27
+ });
30
28
 
31
- With `allowExternalAutomation`, the SDK can run `open -a cmux` and retry a workspace command while cmux starts.
29
+ await providers.cmux.newSurface({
30
+ workspace: workspace.workspaceId,
31
+ type: "browser",
32
+ url: "http://localhost:3000",
33
+ focus: true,
34
+ });
32
35
 
33
- Config-defined operations can request the typed `cmux.open` host capability through the provider facade:
36
+ const terminal = await providers.cmux.newSurface({
37
+ workspace: workspace.workspaceId,
38
+ type: "terminal",
39
+ focus: false,
40
+ });
34
41
 
35
- ```ts
36
- import { workflow } from "@rigkit/sdk";
37
- import { cmux } from "@rigkit/provider-cmux";
42
+ await providers.cmux.send({
43
+ workspace: workspace.workspaceId,
44
+ surface: terminal.surfaceId,
45
+ text: "pnpm dev\n",
46
+ });
38
47
 
39
- export default workflow("site", {
40
- providers: {
41
- cmux: cmux.provider(),
42
- },
43
- })
44
- .sequence("site")
45
- .operation("open", {
46
- run: async ({ providers }) => {
47
- await providers.cmux.open({
48
- name: "site",
49
- ssh: {
50
- host: "devbox.example.com",
51
- username: "root",
52
- sshOptions: ["ServerAliveInterval=15"],
53
- },
54
- cwd: "/workspace/site",
55
- surfaceLayout: "tabs",
56
- terminals: [{ command: "pnpm dev" }],
57
- url: "http://localhost:3000",
58
- });
59
- },
60
- });
48
+ await providers.cmux.selectWorkspace(workspace.workspaceId);
61
49
  ```
62
50
 
63
- Set `surfaceLayout: "tabs"` to open terminals and the optional browser URL as
64
- tabs in the same cmux pane. Omit it, or set `"splits"`, to keep the default
65
- split-pane behavior.
66
-
67
- Local hosts can import `@rigkit/provider-cmux/host` to register the trusted `cmux.open` handler. The Rigkit CLI registers this handler automatically.
51
+ Local hosts can import `@rigkit/provider-cmux/host` to register the trusted
52
+ `cmux.call` handler. The Rigkit CLI registers this handler automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigkit/provider-cmux",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -18,8 +18,8 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@rigkit/sdk": "0.2.13",
22
- "@rigkit/engine": "0.2.13"
21
+ "@rigkit/sdk": "0.2.14",
22
+ "@rigkit/engine": "0.2.14"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/bun": "latest",
@@ -1,14 +1,30 @@
1
- export const CMUX_OPEN_CAPABILITY_ID = "cmux.open";
1
+ export const CMUX_CALL_CAPABILITY_ID = "cmux.call";
2
2
 
3
- export const CMUX_OPEN_SCHEMA_HASH =
4
- "sha256:3a2975cfe53089c6a607da751b55575dc8806bd90132242d4b7e9065a26ae3af";
3
+ export const CMUX_CALL_SCHEMA_HASH =
4
+ "sha256:afcc8ef7251d854c80d1c04d9a98bc9afbd22d2ab15e1ce9fb880452ec17f6cf";
5
5
 
6
- export const CMUX_OPEN_CAPABILITY = {
7
- id: CMUX_OPEN_CAPABILITY_ID,
8
- schemaHash: CMUX_OPEN_SCHEMA_HASH,
6
+ export const CMUX_CALL_CAPABILITY = {
7
+ id: CMUX_CALL_CAPABILITY_ID,
8
+ schemaHash: CMUX_CALL_SCHEMA_HASH,
9
9
  } as const;
10
10
 
11
- export type CmuxOpenSshInput = string | {
11
+ export type CmuxCallMethod =
12
+ | "newWorkspace"
13
+ | "ssh"
14
+ | "newPane"
15
+ | "newSurface"
16
+ | "browserOpen"
17
+ | "send"
18
+ | "portsKick"
19
+ | "selectWorkspace"
20
+ | "waitForRemoteReady";
21
+
22
+ export type CmuxCallInput = {
23
+ method: CmuxCallMethod;
24
+ params?: Record<string, unknown>;
25
+ };
26
+
27
+ export type CmuxSshInput = string | {
12
28
  kind?: "ssh";
13
29
  destination?: string;
14
30
  host?: string;
@@ -21,50 +37,72 @@ export type CmuxOpenSshInput = string | {
21
37
  terminalStartupCommand?: string;
22
38
  autoConnect?: boolean;
23
39
  skipDaemonBootstrap?: boolean;
40
+ name?: string;
41
+ noFocus?: boolean;
24
42
  };
25
43
 
26
- export type CmuxRemoteReadyOptions = {
27
- timeoutMs?: number;
28
- intervalMs?: number;
29
- requireProxy?: boolean;
44
+ export type CmuxWorkspaceInput = {
45
+ name?: string;
46
+ description?: string;
47
+ cwd?: string;
48
+ command?: string;
49
+ focus?: boolean;
30
50
  };
31
51
 
32
- export type CmuxOpenTerminalDirection = "left" | "right" | "up" | "down";
33
- export type CmuxOpenSurfaceLayout = "splits" | "tabs";
52
+ export type CmuxPaneDirection = "left" | "right" | "up" | "down";
53
+ export type CmuxSurfaceType = "terminal" | "browser";
34
54
 
35
- export type CmuxOpenTerminalInput = {
36
- command: string;
37
- cwd?: string;
38
- direction?: CmuxOpenTerminalDirection;
55
+ export type CmuxNewPaneInput = {
56
+ workspace?: string;
57
+ type?: CmuxSurfaceType;
58
+ direction?: CmuxPaneDirection;
59
+ url?: string;
39
60
  focus?: boolean;
40
61
  };
41
62
 
42
- export type CmuxOpenInput = {
43
- name: string;
44
- ssh?: CmuxOpenSshInput;
45
- cwd?: string;
46
- surfaceLayout?: CmuxOpenSurfaceLayout;
47
- terminals?: readonly CmuxOpenTerminalInput[];
63
+ export type CmuxNewSurfaceInput = {
64
+ workspace?: string;
65
+ pane?: string;
66
+ type?: CmuxSurfaceType;
48
67
  url?: string;
49
68
  focus?: boolean;
50
- waitForRemoteReady?: boolean | CmuxRemoteReadyOptions;
51
69
  };
52
70
 
53
- export type CmuxOpenPaneResult = {
54
- paneId?: string;
55
- paneRef?: string;
56
- surfaceId?: string;
57
- surfaceRef?: string;
71
+ export type CmuxBrowserOpenInput = {
72
+ workspace?: string;
73
+ window?: string;
74
+ url?: string;
75
+ focus?: boolean;
76
+ };
77
+
78
+ export type CmuxSendInput = {
79
+ workspace?: string;
80
+ surface?: string;
81
+ text: string;
58
82
  };
59
83
 
60
- export type CmuxOpenResult = {
84
+ export type CmuxPortsKickInput = {
85
+ workspace: string;
86
+ surface?: string;
87
+ reason?: "command" | "refresh";
88
+ };
89
+
90
+ export type CmuxRemoteReadyInput = {
91
+ workspace: string;
92
+ timeoutMs?: number;
93
+ intervalMs?: number;
94
+ requireProxy?: boolean;
95
+ };
96
+
97
+ export type CmuxWorkspaceResult = {
61
98
  sessionId: string;
62
99
  workspaceId: string;
63
100
  workspaceRef?: string;
64
- terminalPanes: CmuxOpenPaneResult[];
65
- browserPane?: CmuxOpenPaneResult;
66
101
  };
67
102
 
68
- export type CmuxOpenSession = CmuxOpenResult & {
69
- closed: Promise<void>;
103
+ export type CmuxPaneResult = {
104
+ paneId?: string;
105
+ paneRef?: string;
106
+ surfaceId?: string;
107
+ surfaceRef?: string;
70
108
  };
package/src/host.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  createCmuxClient,
3
- formatShellCommand,
3
+ type CmuxBrowserOpenOptions,
4
4
  type CmuxClient,
5
5
  type CmuxClientOptions,
6
+ type CmuxNewPaneOptions,
7
+ type CmuxNewSurfaceOptions,
6
8
  type CmuxNewWorkspaceOptions,
7
- type CmuxPane,
8
9
  type CmuxPortsKickOptions,
9
10
  type CmuxSendOptions,
10
11
  type CmuxSshOptions,
11
12
  type CmuxWaitForRemoteOptions,
12
- type CmuxWorkspace,
13
13
  } from "./index.ts";
14
14
  import {
15
15
  defineHostCapability,
@@ -17,19 +17,14 @@ import {
17
17
  type HostCapabilityHandler,
18
18
  } from "@rigkit/sdk/host";
19
19
  import {
20
- CMUX_OPEN_CAPABILITY,
21
- type CmuxOpenInput,
22
- type CmuxOpenPaneResult,
23
- type CmuxOpenResult,
24
- type CmuxOpenSshInput,
25
- type CmuxOpenTerminalDirection,
26
- type CmuxOpenTerminalInput,
27
- type CmuxRemoteReadyOptions,
20
+ CMUX_CALL_CAPABILITY,
21
+ type CmuxCallInput,
22
+ type CmuxSshInput,
28
23
  } from "./capabilities.ts";
29
24
 
30
25
  export type CmuxHostCapabilityHandler = HostCapabilityHandler;
31
26
 
32
- export type CmuxOpenClient = Pick<
27
+ export type CmuxCallClient = Pick<
33
28
  CmuxClient,
34
29
  | "newWorkspace"
35
30
  | "ssh"
@@ -42,138 +37,69 @@ export type CmuxOpenClient = Pick<
42
37
  | "waitForRemoteReady"
43
38
  >;
44
39
 
45
- export type CmuxOpenHostOptions = {
46
- client?: CmuxOpenClient;
40
+ export type CmuxCallHostOptions = {
41
+ client?: CmuxCallClient;
47
42
  clientOptions?: CmuxClientOptions;
48
43
  logger?: (message: string) => void;
49
44
  };
50
45
 
51
- export function createCmuxOpenHostCapability(
52
- options: CmuxOpenHostOptions = {},
46
+ export function createCmuxCallHostCapability(
47
+ options: CmuxCallHostOptions = {},
53
48
  ): CmuxHostCapabilityHandler {
54
- return defineHostCapability(CMUX_OPEN_CAPABILITY.id, {
55
- schemaHash: CMUX_OPEN_CAPABILITY.schemaHash,
49
+ return defineHostCapability(CMUX_CALL_CAPABILITY.id, {
50
+ schemaHash: CMUX_CALL_CAPABILITY.schemaHash,
56
51
  handle: async (params, context) =>
57
- await openCmux(params, {
52
+ await callCmux(params, {
58
53
  ...options,
59
54
  logger: options.logger ?? hostCapabilityLogger(context) ?? options.clientOptions?.logger,
60
55
  }),
61
56
  });
62
57
  }
63
58
 
64
- export const cmuxHostCapabilities = [createCmuxOpenHostCapability()] as const;
59
+ export const cmuxHostCapabilities = [createCmuxCallHostCapability()] as const;
65
60
 
66
- export async function openCmux(
61
+ export async function callCmux(
67
62
  params: unknown,
68
- options: CmuxOpenHostOptions = {},
69
- ): Promise<CmuxOpenResult> {
70
- const input = parseCmuxOpenInput(params);
71
- const logger = cmuxOpenLogger(options);
63
+ options: CmuxCallHostOptions = {},
64
+ ): Promise<unknown> {
65
+ const input = parseCmuxCallInput(params);
66
+ const logger = cmuxCallLogger(options);
72
67
  const cmux = options.client ?? createCmuxClient({
73
68
  ...options.clientOptions,
74
69
  ...(options.logger ? { logger: options.logger } : {}),
75
70
  printCommands: options.clientOptions?.printCommands ?? false,
76
71
  });
77
- let workspace: CmuxWorkspace;
78
- const terminalPanes: CmuxPane[] = [];
79
72
 
80
- logger?.(`cmux: opening ${input.name}`);
81
- if (input.ssh) {
82
- logger?.("cmux: connecting remote workspace");
83
- workspace = await cmux.ssh({
84
- ...cmuxSshOptionsForInput(input.ssh),
85
- name: input.name,
86
- noFocus: input.focus === false,
87
- });
88
- } else {
89
- logger?.("cmux: creating workspace");
90
- const workspaceOptions: CmuxNewWorkspaceOptions = {
91
- name: input.name,
92
- cwd: input.cwd,
93
- focus: input.focus,
94
- };
95
- workspace = await cmux.newWorkspace(workspaceOptions);
96
- }
97
-
98
- const workspaceId = workspace.id ?? workspace.handle;
99
- const useTabLayout = input.surfaceLayout === "tabs";
100
-
101
- for (const terminal of input.terminals ?? []) {
102
- const cwd = terminal.cwd ?? input.cwd;
103
- logger?.(cwd ? `cmux: starting terminal in ${cwd}` : "cmux: starting terminal");
104
- const terminalPane = useTabLayout
105
- ? await cmux.newSurface({
106
- workspace: workspaceId,
107
- type: "terminal",
108
- focus: terminal.focus ?? true,
109
- })
110
- : await cmux.newPane({
111
- workspace: workspaceId,
112
- type: "terminal",
113
- direction: terminal.direction ?? "down",
114
- focus: terminal.focus ?? true,
115
- });
116
- terminalPanes.push(terminalPane);
117
- const sendOptions: CmuxSendOptions = {
118
- workspace: workspaceId,
119
- surface: terminalPane.surface,
120
- text: commandForTerminal(terminal, input),
121
- };
122
- await cmux.send(sendOptions);
123
- }
124
-
125
- const waitOptions = remoteReadyOptionsForInput(input);
126
- if (input.ssh && waitOptions) {
127
- logger?.("cmux: waiting for remote ports");
128
- await cmux.waitForRemoteReady(workspaceId, waitOptions);
129
- }
130
-
131
- if (input.ssh && terminalPanes.some((pane) => pane.surface)) {
132
- logger?.("cmux: refreshing remote ports");
133
- for (const pane of terminalPanes) {
134
- if (!pane.surface) continue;
135
- const kickOptions: CmuxPortsKickOptions = {
136
- workspace: workspaceId,
137
- surface: pane.surface,
138
- reason: "command",
139
- };
140
- await cmux.portsKick(kickOptions);
73
+ logger?.(`cmux: ${input.method}`);
74
+ switch (input.method) {
75
+ case "newWorkspace":
76
+ return await cmux.newWorkspace(input.params as CmuxNewWorkspaceOptions);
77
+ case "ssh":
78
+ return await cmux.ssh(cmuxSshOptionsForInput(input.params));
79
+ case "newPane":
80
+ return await cmux.newPane(input.params as CmuxNewPaneOptions);
81
+ case "newSurface":
82
+ return await cmux.newSurface(input.params as CmuxNewSurfaceOptions);
83
+ case "browserOpen":
84
+ return await cmux.browserOpen(input.params as CmuxBrowserOpenOptions);
85
+ case "send":
86
+ return await cmux.send(input.params as CmuxSendOptions);
87
+ case "portsKick":
88
+ return await cmux.portsKick(input.params as CmuxPortsKickOptions);
89
+ case "selectWorkspace":
90
+ await cmux.selectWorkspace(requiredString(input.params, "workspace"));
91
+ return "OK";
92
+ case "waitForRemoteReady": {
93
+ const { workspace, ...waitOptions } = input.params;
94
+ if (typeof workspace !== "string" || workspace.trim() === "") {
95
+ throw new Error(`cmux.call waitForRemoteReady requires params.workspace`);
96
+ }
97
+ return await cmux.waitForRemoteReady(workspace, waitOptions as CmuxWaitForRemoteOptions);
141
98
  }
142
99
  }
143
-
144
- let browserPane: CmuxPane | undefined;
145
- if (input.url) {
146
- logger?.(`cmux: opening ${input.url}`);
147
- browserPane = useTabLayout
148
- ? await cmux.newSurface({
149
- workspace: workspaceId,
150
- type: "browser",
151
- url: input.url,
152
- focus: input.focus !== false,
153
- })
154
- : await cmux.browserOpen({
155
- workspace: workspaceId,
156
- url: input.url,
157
- focus: input.focus !== false,
158
- });
159
- }
160
-
161
- if (input.focus !== false) {
162
- logger?.("cmux: focusing workspace");
163
- await cmux.selectWorkspace(workspaceId);
164
- }
165
-
166
- logger?.(`cmux: ready ${input.name}`);
167
- return {
168
- sessionId: workspaceId,
169
- workspaceId,
170
- ...(workspace.ref ? { workspaceRef: workspace.ref } : {}),
171
- terminalPanes: terminalPanes.map(paneResultForCmuxPane),
172
- ...(browserPane ? { browserPane: paneResultForCmuxPane(browserPane) } : {}),
173
- };
174
100
  }
175
101
 
176
- function cmuxOpenLogger(options: CmuxOpenHostOptions): ((message: string) => void) | undefined {
102
+ function cmuxCallLogger(options: CmuxCallHostOptions): ((message: string) => void) | undefined {
177
103
  return options.logger ?? options.clientOptions?.logger;
178
104
  }
179
105
 
@@ -182,28 +108,56 @@ function hostCapabilityLogger(context: HostCapabilityContext | undefined): ((mes
182
108
  return (message) => context.log(message, { label: "cmux" });
183
109
  }
184
110
 
185
- export function parseCmuxOpenInput(value: unknown): CmuxOpenInput {
186
- if (!isRecord(value)) throw new Error(`cmux.open requires an object input`);
187
- const name = requiredString(value, "name");
111
+ export function parseCmuxCallInput(value: unknown): CmuxCallInput & { params: Record<string, unknown> } {
112
+ if (!isRecord(value)) throw new Error(`cmux.call requires an object input`);
113
+ const method = requiredString(value, "method");
114
+ if (!isCmuxCallMethod(method)) {
115
+ throw new Error(`cmux.call method is not supported: ${method}`);
116
+ }
117
+ const params = value.params === undefined ? {} : value.params;
118
+ if (!isRecord(params)) throw new Error(`cmux.call params must be an object`);
119
+ return { method, params };
120
+ }
121
+
122
+ function isCmuxCallMethod(value: string): value is CmuxCallInput["method"] {
123
+ return [
124
+ "newWorkspace",
125
+ "ssh",
126
+ "newPane",
127
+ "newSurface",
128
+ "browserOpen",
129
+ "send",
130
+ "portsKick",
131
+ "selectWorkspace",
132
+ "waitForRemoteReady",
133
+ ].includes(value);
134
+ }
135
+
136
+ function cmuxSshOptionsForInput(ssh: Record<string, unknown>): CmuxSshOptions {
137
+ const parsed = parseSshInput(ssh);
138
+ if (typeof parsed === "string") return { destination: parsed };
139
+
140
+ const destination = parsed.destination ?? sshDestination(parsed);
188
141
  return {
189
- name,
190
- ...(value.ssh !== undefined ? { ssh: parseSshInput(value.ssh) } : {}),
191
- ...optionalStringField(value, "cwd"),
192
- ...optionalSurfaceLayoutField(value, "surfaceLayout"),
193
- ...(value.terminals !== undefined ? { terminals: parseTerminalInputs(value.terminals) } : {}),
194
- ...optionalStringField(value, "url"),
195
- ...optionalBooleanField(value, "focus"),
196
- ...(value.waitForRemoteReady !== undefined
197
- ? { waitForRemoteReady: parseRemoteReadyOptions(value.waitForRemoteReady) }
198
- : {}),
142
+ destination,
143
+ ...(parsed.port !== undefined ? { port: parsed.port } : {}),
144
+ ...(parsed.identity !== undefined ? { identity: parsed.identity } : {}),
145
+ ...(parsed.sshOptions?.length ? { sshOptions: parsed.sshOptions } : {}),
146
+ ...(parsed.remoteCommandArgs !== undefined ? { remoteCommandArgs: parsed.remoteCommandArgs } : {}),
147
+ ...(parsed.initialCommand !== undefined ? { initialCommand: parsed.initialCommand } : {}),
148
+ ...(parsed.terminalStartupCommand !== undefined ? { terminalStartupCommand: parsed.terminalStartupCommand } : {}),
149
+ ...(parsed.autoConnect !== undefined ? { autoConnect: parsed.autoConnect } : {}),
150
+ ...(parsed.skipDaemonBootstrap !== undefined ? { skipDaemonBootstrap: parsed.skipDaemonBootstrap } : {}),
151
+ ...(parsed.name !== undefined ? { name: parsed.name } : {}),
152
+ ...(parsed.noFocus !== undefined ? { noFocus: parsed.noFocus } : {}),
199
153
  };
200
154
  }
201
155
 
202
- function parseSshInput(value: unknown): CmuxOpenSshInput {
156
+ function parseSshInput(value: unknown): CmuxSshInput {
203
157
  if (typeof value === "string" && value.trim()) return value.trim();
204
- if (!isRecord(value)) throw new Error(`cmux.open ssh must be a string or object`);
158
+ if (!isRecord(value)) throw new Error(`cmux.call ssh params must be an object`);
205
159
  if (value.kind !== undefined && value.kind !== "ssh") {
206
- throw new Error(`cmux.open ssh.kind must be "ssh"`);
160
+ throw new Error(`cmux.call ssh.kind must be "ssh"`);
207
161
  }
208
162
  return {
209
163
  ...optionalStringField(value, "destination"),
@@ -217,84 +171,21 @@ function parseSshInput(value: unknown): CmuxOpenSshInput {
217
171
  ...optionalStringField(value, "terminalStartupCommand"),
218
172
  ...optionalBooleanField(value, "autoConnect"),
219
173
  ...optionalBooleanField(value, "skipDaemonBootstrap"),
174
+ ...optionalStringField(value, "name"),
175
+ ...optionalBooleanField(value, "noFocus"),
220
176
  };
221
177
  }
222
178
 
223
- function parseRemoteReadyOptions(value: unknown): boolean | CmuxRemoteReadyOptions {
224
- if (typeof value === "boolean") return value;
225
- if (!isRecord(value)) throw new Error(`cmux.open waitForRemoteReady must be a boolean or object`);
226
- return {
227
- ...optionalNumberField(value, "timeoutMs"),
228
- ...optionalNumberField(value, "intervalMs"),
229
- ...optionalBooleanField(value, "requireProxy"),
230
- };
231
- }
232
-
233
- function cmuxSshOptionsForInput(ssh: CmuxOpenSshInput): CmuxSshOptions {
234
- if (typeof ssh === "string") return { destination: ssh };
235
-
236
- const destination = ssh.destination ?? sshDestination(ssh);
237
- return {
238
- destination,
239
- ...(ssh.port !== undefined ? { port: ssh.port } : {}),
240
- ...(ssh.identity !== undefined ? { identity: ssh.identity } : {}),
241
- ...(ssh.sshOptions?.length ? { sshOptions: ssh.sshOptions } : {}),
242
- ...(ssh.remoteCommandArgs !== undefined ? { remoteCommandArgs: ssh.remoteCommandArgs } : {}),
243
- ...(ssh.initialCommand !== undefined ? { initialCommand: ssh.initialCommand } : {}),
244
- ...(ssh.terminalStartupCommand !== undefined ? { terminalStartupCommand: ssh.terminalStartupCommand } : {}),
245
- ...(ssh.autoConnect !== undefined ? { autoConnect: ssh.autoConnect } : {}),
246
- ...(ssh.skipDaemonBootstrap !== undefined ? { skipDaemonBootstrap: ssh.skipDaemonBootstrap } : {}),
247
- };
248
- }
249
-
250
- function sshDestination(ssh: Extract<CmuxOpenSshInput, object>): string {
251
- if (!ssh.host) throw new Error(`cmux.open ssh.host is required when ssh.destination is omitted`);
252
- if (!ssh.username) throw new Error(`cmux.open ssh.username is required when ssh.destination is omitted`);
179
+ function sshDestination(ssh: Extract<CmuxSshInput, object>): string {
180
+ if (!ssh.host) throw new Error(`cmux.call ssh.host is required when ssh.destination is omitted`);
181
+ if (!ssh.username) throw new Error(`cmux.call ssh.username is required when ssh.destination is omitted`);
253
182
  return `${ssh.username}@${ssh.host}`;
254
183
  }
255
184
 
256
- function parseTerminalInputs(value: unknown): CmuxOpenTerminalInput[] {
257
- if (!Array.isArray(value)) throw new Error(`cmux.open terminals must be an array`);
258
- return value.map((item, index) => parseTerminalInput(item, index));
259
- }
260
-
261
- function parseTerminalInput(value: unknown, index: number): CmuxOpenTerminalInput {
262
- if (!isRecord(value)) throw new Error(`cmux.open terminals[${index}] must be an object`);
263
- return {
264
- command: requiredString(value, "command"),
265
- ...optionalStringField(value, "cwd"),
266
- ...optionalTerminalDirectionField(value, "direction"),
267
- ...optionalBooleanField(value, "focus"),
268
- };
269
- }
270
-
271
- function commandForTerminal(terminal: CmuxOpenTerminalInput, input: CmuxOpenInput): string {
272
- const cwd = terminal.cwd ?? input.cwd;
273
- const prefix = cwd ? `${formatShellCommand(["cd", cwd])} && ` : "";
274
- return `${prefix}${terminal.command}\n`;
275
- }
276
-
277
- function paneResultForCmuxPane(pane: CmuxPane): CmuxOpenPaneResult {
278
- return {
279
- ...(pane.pane ? { paneId: pane.pane } : {}),
280
- ...(pane.paneRef ? { paneRef: pane.paneRef } : {}),
281
- ...(pane.surface ? { surfaceId: pane.surface } : {}),
282
- ...(pane.surfaceRef ? { surfaceRef: pane.surfaceRef } : {}),
283
- };
284
- }
285
-
286
- function remoteReadyOptionsForInput(input: CmuxOpenInput): CmuxWaitForRemoteOptions | false {
287
- if (input.waitForRemoteReady === false) return false;
288
- if (input.waitForRemoteReady === true || input.waitForRemoteReady === undefined) {
289
- return input.url ? {} : false;
290
- }
291
- return input.waitForRemoteReady;
292
- }
293
-
294
185
  function requiredString(record: Record<string, unknown>, key: string): string {
295
186
  const value = record[key];
296
187
  if (typeof value !== "string" || value.trim() === "") {
297
- throw new Error(`cmux.open ${key} must be a non-empty string`);
188
+ throw new Error(`cmux.call ${key} must be a non-empty string`);
298
189
  }
299
190
  return value.trim();
300
191
  }
@@ -302,34 +193,32 @@ function requiredString(record: Record<string, unknown>, key: string): string {
302
193
  function optionalStringField(
303
194
  record: Record<string, unknown>,
304
195
  key: string,
305
- expected?: string,
306
196
  ): Record<string, string> {
307
197
  const value = record[key];
308
198
  if (value === undefined) return {};
309
199
  if (typeof value !== "string" || value.trim() === "") {
310
- throw new Error(`cmux.open ${key} must be a non-empty string`);
200
+ throw new Error(`cmux.call ${key} must be a non-empty string`);
311
201
  }
312
- const normalized = value.trim();
313
- if (expected !== undefined && normalized !== expected) {
314
- throw new Error(`cmux.open ${key} must be "${expected}"`);
315
- }
316
- return { [key]: normalized };
202
+ return { [key]: value.trim() };
317
203
  }
318
204
 
319
- function optionalStringArrayField(record: Record<string, unknown>, key: string): Record<string, string[]> {
205
+ function optionalStringArrayField(
206
+ record: Record<string, unknown>,
207
+ key: string,
208
+ ): Record<string, string[]> {
320
209
  const value = record[key];
321
210
  if (value === undefined) return {};
322
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
323
- throw new Error(`cmux.open ${key} must be an array of strings`);
211
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
212
+ throw new Error(`cmux.call ${key} must be an array of strings`);
324
213
  }
325
- return { [key]: value };
214
+ return { [key]: value.map((item) => item.trim()) };
326
215
  }
327
216
 
328
217
  function optionalNumberField(record: Record<string, unknown>, key: string): Record<string, number> {
329
218
  const value = record[key];
330
219
  if (value === undefined) return {};
331
220
  if (typeof value !== "number" || !Number.isFinite(value)) {
332
- throw new Error(`cmux.open ${key} must be a finite number`);
221
+ throw new Error(`cmux.call ${key} must be a finite number`);
333
222
  }
334
223
  return { [key]: value };
335
224
  }
@@ -337,31 +226,7 @@ function optionalNumberField(record: Record<string, unknown>, key: string): Reco
337
226
  function optionalBooleanField(record: Record<string, unknown>, key: string): Record<string, boolean> {
338
227
  const value = record[key];
339
228
  if (value === undefined) return {};
340
- if (typeof value !== "boolean") throw new Error(`cmux.open ${key} must be a boolean`);
341
- return { [key]: value };
342
- }
343
-
344
- function optionalTerminalDirectionField(
345
- record: Record<string, unknown>,
346
- key: string,
347
- ): Record<string, CmuxOpenTerminalDirection> {
348
- const value = record[key];
349
- if (value === undefined) return {};
350
- if (value !== "left" && value !== "right" && value !== "up" && value !== "down") {
351
- throw new Error(`cmux.open ${key} must be "left", "right", "up", or "down"`);
352
- }
353
- return { [key]: value };
354
- }
355
-
356
- function optionalSurfaceLayoutField(
357
- record: Record<string, unknown>,
358
- key: string,
359
- ): Record<string, "splits" | "tabs"> {
360
- const value = record[key];
361
- if (value === undefined) return {};
362
- if (value !== "splits" && value !== "tabs") {
363
- throw new Error(`cmux.open ${key} must be "splits" or "tabs"`);
364
- }
229
+ if (typeof value !== "boolean") throw new Error(`cmux.call ${key} must be a boolean`);
365
230
  return { [key]: value };
366
231
  }
367
232
 
package/src/index.test.ts CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  type CmuxRpcResult,
19
19
  type CmuxRuntime,
20
20
  } from "./index.ts";
21
- import { openCmux, type CmuxOpenClient } from "./host.ts";
21
+ import { callCmux, type CmuxCallClient } from "./host.ts";
22
22
 
23
23
  describe("cmux sdk", () => {
24
24
  test("parses workspace refs from cmux text output", () => {
@@ -439,52 +439,53 @@ describe("cmux sdk", () => {
439
439
  expect(() => cmux.run(["bad"])).toThrow(CmuxCommandError);
440
440
  });
441
441
 
442
- test("handles cmux.open host capability for an ssh workspace with tabs", async () => {
442
+ test("handles raw cmux.call host capability requests", async () => {
443
443
  const calls: Array<{ method: string; params: unknown }> = [];
444
444
  const logs: string[] = [];
445
445
  const client = fakeOpenClient(calls);
446
446
 
447
- const result = await openCmux({
448
- name: "website",
449
- ssh: {
447
+ const workspace = await callCmux({
448
+ method: "ssh",
449
+ params: {
450
450
  kind: "ssh",
451
451
  destination: "vm_123,token_123@vm-ssh.freestyle.sh",
452
+ name: "website",
452
453
  sshOptions: ["ServerAliveInterval=15"],
453
454
  },
454
- cwd: "/workspace/site",
455
- surfaceLayout: "tabs",
456
- terminals: [
457
- { command: "pnpm dev" },
458
- { command: "codex", focus: false },
459
- ],
460
- url: "http://localhost:4321",
461
- }, { client, logger: (message) => logs.push(message) });
462
-
463
- expect(result).toEqual({
464
- sessionId: "workspace-1",
465
- workspaceId: "workspace-1",
466
- workspaceRef: "workspace:1",
467
- terminalPanes: [
468
- {
469
- paneId: "pane-1",
470
- paneRef: "pane:1",
471
- surfaceId: "surface-1",
472
- surfaceRef: "surface:1",
473
- },
474
- {
475
- paneId: "pane-2",
476
- paneRef: "pane:2",
477
- surfaceId: "surface-2",
478
- surfaceRef: "surface:2",
479
- },
480
- ],
481
- browserPane: {
482
- paneId: "pane-3",
483
- paneRef: "pane:3",
484
- surfaceId: "surface-3",
485
- surfaceRef: "surface:3",
455
+ }, { client, logger: (message: string) => logs.push(message) });
456
+ const browser = await callCmux({
457
+ method: "newSurface",
458
+ params: {
459
+ workspace: "workspace-1",
460
+ type: "browser",
461
+ url: "http://localhost:4321",
462
+ focus: true,
463
+ },
464
+ }, { client, logger: (message: string) => logs.push(message) });
465
+ const terminal = await callCmux({
466
+ method: "newSurface",
467
+ params: {
468
+ workspace: "workspace-1",
469
+ type: "terminal",
470
+ focus: false,
486
471
  },
487
- });
472
+ }, { client, logger: (message: string) => logs.push(message) });
473
+ await callCmux({
474
+ method: "send",
475
+ params: {
476
+ workspace: "workspace-1",
477
+ surface: "surface-2",
478
+ text: "pnpm dev\n",
479
+ },
480
+ }, { client, logger: (message: string) => logs.push(message) });
481
+ await callCmux({
482
+ method: "selectWorkspace",
483
+ params: { workspace: "workspace-1" },
484
+ }, { client, logger: (message: string) => logs.push(message) });
485
+
486
+ expect(workspace).toEqual({ handle: "workspace-1", id: "workspace-1", ref: "workspace:1" });
487
+ expect(browser).toMatchObject({ surface: "surface-1" });
488
+ expect(terminal).toMatchObject({ surface: "surface-2" });
488
489
  expect(calls).toEqual([
489
490
  {
490
491
  method: "ssh",
@@ -498,18 +499,11 @@ describe("cmux sdk", () => {
498
499
  method: "newSurface",
499
500
  params: {
500
501
  workspace: "workspace-1",
501
- type: "terminal",
502
+ type: "browser",
503
+ url: "http://localhost:4321",
502
504
  focus: true,
503
505
  },
504
506
  },
505
- {
506
- method: "send",
507
- params: {
508
- workspace: "workspace-1",
509
- surface: "surface-1",
510
- text: "cd /workspace/site && pnpm dev\n",
511
- },
512
- },
513
507
  {
514
508
  method: "newSurface",
515
509
  params: {
@@ -523,39 +517,7 @@ describe("cmux sdk", () => {
523
517
  params: {
524
518
  workspace: "workspace-1",
525
519
  surface: "surface-2",
526
- text: "cd /workspace/site && codex\n",
527
- },
528
- },
529
- {
530
- method: "waitForRemoteReady",
531
- params: {
532
- workspace: "workspace-1",
533
- options: {},
534
- },
535
- },
536
- {
537
- method: "portsKick",
538
- params: {
539
- workspace: "workspace-1",
540
- surface: "surface-1",
541
- reason: "command",
542
- },
543
- },
544
- {
545
- method: "portsKick",
546
- params: {
547
- workspace: "workspace-1",
548
- surface: "surface-2",
549
- reason: "command",
550
- },
551
- },
552
- {
553
- method: "newSurface",
554
- params: {
555
- workspace: "workspace-1",
556
- type: "browser",
557
- url: "http://localhost:4321",
558
- focus: true,
520
+ text: "pnpm dev\n",
559
521
  },
560
522
  },
561
523
  {
@@ -563,17 +525,12 @@ describe("cmux sdk", () => {
563
525
  params: "workspace-1",
564
526
  },
565
527
  ]);
566
- expect(calls[0]?.params).not.toHaveProperty("terminalStartupCommand");
567
528
  expect(logs).toEqual([
568
- "cmux: opening website",
569
- "cmux: connecting remote workspace",
570
- "cmux: starting terminal in /workspace/site",
571
- "cmux: starting terminal in /workspace/site",
572
- "cmux: waiting for remote ports",
573
- "cmux: refreshing remote ports",
574
- "cmux: opening http://localhost:4321",
575
- "cmux: focusing workspace",
576
- "cmux: ready website",
529
+ "cmux: ssh",
530
+ "cmux: newSurface",
531
+ "cmux: newSurface",
532
+ "cmux: send",
533
+ "cmux: selectWorkspace",
577
534
  ]);
578
535
  });
579
536
 
@@ -581,11 +538,12 @@ describe("cmux sdk", () => {
581
538
  const calls: Array<{ method: string; params: unknown }> = [];
582
539
  const client = fakeOpenClient(calls);
583
540
 
584
- await openCmux({
585
- name: "website",
586
- ssh: {
541
+ await callCmux({
542
+ method: "ssh",
543
+ params: {
587
544
  kind: "ssh",
588
545
  destination: "vm_123,token_123@vm-ssh.freestyle.sh",
546
+ name: "website",
589
547
  terminalStartupCommand: "ssh -tt vm_123:token_123@vm-ssh.freestyle.sh",
590
548
  },
591
549
  }, { client });
@@ -600,7 +558,7 @@ describe("cmux sdk", () => {
600
558
  });
601
559
  });
602
560
 
603
- test("exposes a provider facade that requests cmux.open from the local host", async () => {
561
+ test("exposes a provider facade that requests raw cmux calls from the local host", async () => {
604
562
  const definition = cmux.provider();
605
563
  expect(definition.providerId).toBe("cmux");
606
564
  expect(definition.plugin).toBe(cmuxProviderPlugin);
@@ -624,77 +582,72 @@ describe("cmux sdk", () => {
624
582
  open: async () => {},
625
583
  requestCapability: async <Result,>(capability: string, params: unknown, options: unknown) => {
626
584
  requests.push({ capability, params, options });
585
+ const method = (params as { method?: string }).method;
586
+ if (method === "newSurface") {
587
+ return { surface: "surface-1", pane: "pane-1" } as Result;
588
+ }
589
+ if (method === "send") return "OK" as Result;
627
590
  return { sessionId: "workspace-1", workspaceId: "workspace-1" } as Result;
628
591
  },
629
592
  },
630
593
  }) as CmuxRuntime;
631
594
 
632
- const session = await runtime.open({ name: "workspace" });
595
+ const workspace = await runtime.ssh({
596
+ destination: "vm_123,token_123@vm-ssh.freestyle.sh",
597
+ name: "workspace",
598
+ });
599
+ const terminal = await runtime.newSurface({
600
+ workspace: workspace.workspaceId,
601
+ type: "terminal",
602
+ focus: true,
603
+ });
604
+ await runtime.send({
605
+ workspace: workspace.workspaceId,
606
+ surface: terminal.surfaceId,
607
+ text: "git status\n",
608
+ });
633
609
 
634
610
  expect(requests).toEqual([
635
611
  {
636
- capability: "cmux.open",
637
- params: { name: "workspace" },
612
+ capability: "cmux.call",
613
+ params: {
614
+ method: "ssh",
615
+ params: {
616
+ destination: "vm_123,token_123@vm-ssh.freestyle.sh",
617
+ name: "workspace",
618
+ },
619
+ },
638
620
  options: { nodePath: "operation.open" },
639
621
  },
640
- ]);
641
- expect(session.sessionId).toBe("workspace-1");
642
-
643
- let closed = false;
644
- void session.closed.then(() => {
645
- closed = true;
646
- });
647
- await Bun.sleep(5);
648
- expect(closed).toBe(false);
649
- });
650
-
651
- test("uses host capability close reporting when the runtime provides it", async () => {
652
- const controller = await cmuxProviderPlugin.createProvider({
653
- provider: { providerId: "cmux", config: {} },
654
- storage: memoryProviderStorage("cmux"),
655
- hostStorage: memoryProviderStorage("cmux"),
656
- local: { open: async () => {} },
657
- });
658
- let resolveClosed!: () => void;
659
- const runtime = await controller.runtime({
660
- workflow: "test",
661
- nodePath: "operation.open",
662
- emit: () => {},
663
- interaction: {
664
- present: async <Result,>() => undefined as Result,
622
+ {
623
+ capability: "cmux.call",
624
+ params: {
625
+ method: "newSurface",
626
+ params: {
627
+ workspace: "workspace-1",
628
+ type: "terminal",
629
+ focus: true,
630
+ },
631
+ },
632
+ options: { nodePath: "operation.open" },
665
633
  },
666
- metadata: () => {},
667
- local: {
668
- open: async () => {},
669
- requestCapabilitySession: async <Result,>(capability: string, params: unknown, options: unknown) => {
670
- expect(capability).toBe("cmux.open");
671
- expect(params).toEqual({ name: "workspace" });
672
- expect(options).toEqual({ nodePath: "operation.open" });
673
- return {
674
- result: { sessionId: "workspace-1", workspaceId: "workspace-1" } as Result,
675
- closed: new Promise<void>((resolve) => {
676
- resolveClosed = resolve;
677
- }),
678
- };
634
+ {
635
+ capability: "cmux.call",
636
+ params: {
637
+ method: "send",
638
+ params: {
639
+ workspace: "workspace-1",
640
+ surface: "surface-1",
641
+ text: "git status\n",
642
+ },
679
643
  },
644
+ options: { nodePath: "operation.open" },
680
645
  },
681
- }) as CmuxRuntime;
682
-
683
- const session = await runtime.open({ name: "workspace" });
684
- let closed = false;
685
- void session.closed.then(() => {
686
- closed = true;
687
- });
688
-
689
- await Bun.sleep(5);
690
- expect(closed).toBe(false);
691
- resolveClosed();
692
- await session.closed;
693
- expect(closed).toBe(true);
646
+ ]);
694
647
  });
695
648
  });
696
649
 
697
- function fakeOpenClient(calls: Array<{ method: string; params: unknown }>): CmuxOpenClient {
650
+ function fakeOpenClient(calls: Array<{ method: string; params: unknown }>): CmuxCallClient {
698
651
  let terminalPaneIndex = 0;
699
652
  return {
700
653
  async newWorkspace(params) {
package/src/index.ts CHANGED
@@ -492,26 +492,32 @@ export function formatShellCommand(args: readonly string[]): string {
492
492
 
493
493
  export { RIGKIT_PROVIDER_CMUX_VERSION } from "./version.ts";
494
494
  export {
495
- CMUX_OPEN_CAPABILITY,
496
- CMUX_OPEN_CAPABILITY_ID,
497
- CMUX_OPEN_SCHEMA_HASH,
498
- type CmuxOpenInput,
499
- type CmuxOpenPaneResult,
500
- type CmuxOpenResult,
501
- type CmuxOpenSession,
502
- type CmuxOpenSurfaceLayout,
503
- type CmuxOpenSshInput,
504
- type CmuxOpenTerminalDirection,
505
- type CmuxOpenTerminalInput,
506
- type CmuxRemoteReadyOptions,
495
+ CMUX_CALL_CAPABILITY,
496
+ CMUX_CALL_CAPABILITY_ID,
497
+ CMUX_CALL_SCHEMA_HASH,
498
+ type CmuxBrowserOpenInput,
499
+ type CmuxCallInput,
500
+ type CmuxCallMethod,
501
+ type CmuxNewPaneInput,
502
+ type CmuxNewSurfaceInput,
503
+ type CmuxPaneDirection,
504
+ type CmuxPaneResult,
505
+ type CmuxPortsKickInput,
506
+ type CmuxRemoteReadyInput,
507
+ type CmuxSendInput,
508
+ type CmuxSshInput,
509
+ type CmuxSurfaceType,
510
+ type CmuxWorkspaceInput,
511
+ type CmuxWorkspaceResult,
507
512
  } from "./capabilities.ts";
508
513
  export {
509
514
  CMUX_PROVIDER_ID,
510
515
  cmux,
511
516
  cmuxProviderPlugin,
512
517
  provider as defineCmuxProvider,
513
- parseCmuxOpenResult,
514
- requestCmuxOpen,
518
+ parseCmuxPaneResult,
519
+ parseCmuxWorkspaceResult,
520
+ requestCmuxCall,
515
521
  type CmuxProviderDefinition,
516
522
  type CmuxRuntime,
517
523
  } from "./provider.ts";
package/src/provider.ts CHANGED
@@ -5,17 +5,33 @@ import {
5
5
  } from "@rigkit/sdk";
6
6
  import type { BaseProviderPlugin, WorkflowProviderController } from "@rigkit/engine";
7
7
  import {
8
- CMUX_OPEN_CAPABILITY_ID,
9
- type CmuxOpenInput,
10
- type CmuxOpenPaneResult,
11
- type CmuxOpenResult,
12
- type CmuxOpenSession,
8
+ CMUX_CALL_CAPABILITY_ID,
9
+ type CmuxBrowserOpenInput,
10
+ type CmuxCallInput,
11
+ type CmuxNewPaneInput,
12
+ type CmuxNewSurfaceInput,
13
+ type CmuxPaneResult,
14
+ type CmuxPortsKickInput,
15
+ type CmuxRemoteReadyInput,
16
+ type CmuxSendInput,
17
+ type CmuxSshInput,
18
+ type CmuxWorkspaceInput,
19
+ type CmuxWorkspaceResult,
13
20
  } from "./capabilities.ts";
14
21
 
15
22
  export const CMUX_PROVIDER_ID = "cmux";
16
23
 
17
24
  export type CmuxRuntime = {
18
- open(input: CmuxOpenInput): Promise<CmuxOpenSession>;
25
+ call(input: CmuxCallInput): Promise<unknown>;
26
+ newWorkspace(input?: CmuxWorkspaceInput): Promise<CmuxWorkspaceResult>;
27
+ ssh(input: CmuxSshInput): Promise<CmuxWorkspaceResult>;
28
+ newPane(input?: CmuxNewPaneInput): Promise<CmuxPaneResult>;
29
+ newSurface(input?: CmuxNewSurfaceInput): Promise<CmuxPaneResult>;
30
+ browserOpen(input?: CmuxBrowserOpenInput): Promise<CmuxPaneResult>;
31
+ send(input: CmuxSendInput): Promise<string>;
32
+ portsKick(input: CmuxPortsKickInput): Promise<string>;
33
+ selectWorkspace(workspace: string): Promise<void>;
34
+ waitForRemoteReady(input: CmuxRemoteReadyInput): Promise<unknown>;
19
35
  };
20
36
 
21
37
  export type CmuxProviderDefinition = WorkflowProviderDefinition<
@@ -45,75 +61,69 @@ export const cmuxProviderPlugin: BaseProviderPlugin = {
45
61
  };
46
62
 
47
63
  function createCmuxRuntime(local: LocalWorkspaceRuntime, nodePath: string): CmuxRuntime {
64
+ const request = (method: CmuxCallInput["method"], params?: Record<string, unknown>) =>
65
+ requestCmuxCall(local, { method, ...(params ? { params } : {}) }, { nodePath });
66
+
48
67
  return {
49
- open: async (input) => await requestCmuxOpen(local, input, { nodePath }),
68
+ call: async (input) => await requestCmuxCall(local, input, { nodePath }),
69
+ newWorkspace: async (input = {}) =>
70
+ parseCmuxWorkspaceResult(await request("newWorkspace", input)),
71
+ ssh: async (input) =>
72
+ parseCmuxWorkspaceResult(await request("ssh", typeof input === "string" ? { destination: input } : input)),
73
+ newPane: async (input = {}) => parseCmuxPaneResult(await request("newPane", input)),
74
+ newSurface: async (input = {}) => parseCmuxPaneResult(await request("newSurface", input)),
75
+ browserOpen: async (input = {}) => parseCmuxPaneResult(await request("browserOpen", input)),
76
+ send: async (input) => String(await request("send", input)),
77
+ portsKick: async (input) => String(await request("portsKick", input)),
78
+ selectWorkspace: async (workspace) => {
79
+ await request("selectWorkspace", { workspace });
80
+ },
81
+ waitForRemoteReady: async (input) => await request("waitForRemoteReady", input),
50
82
  };
51
83
  }
52
84
 
53
- export async function requestCmuxOpen(
85
+ export async function requestCmuxCall(
54
86
  local: LocalWorkspaceRuntime,
55
- input: CmuxOpenInput,
87
+ input: CmuxCallInput,
56
88
  options: { nodePath?: string } = {},
57
- ): Promise<CmuxOpenSession> {
58
- if (local.requestCapabilitySession) {
59
- const session = await local.requestCapabilitySession<CmuxOpenResult>(CMUX_OPEN_CAPABILITY_ID, input, options);
60
- return {
61
- ...parseCmuxOpenResult(session.result),
62
- closed: session.closed,
63
- };
64
- }
89
+ ): Promise<unknown> {
65
90
  if (!local.requestCapability) {
66
- throw new Error(`Host capability ${CMUX_OPEN_CAPABILITY_ID} is unavailable in this runtime`);
91
+ throw new Error(`Host capability ${CMUX_CALL_CAPABILITY_ID} is unavailable in this runtime`);
67
92
  }
68
- const result = parseCmuxOpenResult(
69
- await local.requestCapability(CMUX_OPEN_CAPABILITY_ID, input, options),
70
- );
71
- return {
72
- ...result,
73
- closed: new Promise<void>(() => {}),
74
- };
93
+ return await local.requestCapability(CMUX_CALL_CAPABILITY_ID, input, options);
75
94
  }
76
95
 
77
- export function parseCmuxOpenResult(value: unknown): CmuxOpenResult {
78
- if (!isRecord(value)) throw new Error(`cmux.open returned a non-object result`);
79
- const sessionId = stringField(value, "sessionId");
80
- if (!sessionId) throw new Error(`cmux.open result is missing sessionId`);
81
- const workspaceId = stringField(value, "workspaceId") ?? sessionId;
96
+ export function parseCmuxWorkspaceResult(value: unknown): CmuxWorkspaceResult {
97
+ if (!isRecord(value)) throw new Error(`cmux.call returned a non-object workspace result`);
98
+ const workspaceId = stringField(value, "workspaceId") ?? stringField(value, "id") ?? stringField(value, "handle");
99
+ if (!workspaceId) throw new Error(`cmux.call workspace result is missing workspace id`);
82
100
  return {
83
- sessionId,
101
+ sessionId: stringField(value, "sessionId") ?? workspaceId,
84
102
  workspaceId,
85
- ...optionalStringField(value, "workspaceRef"),
86
- terminalPanes: arrayField(value, "terminalPanes", parseCmuxOpenPaneResult),
87
- ...(value.browserPane !== undefined ? { browserPane: parseCmuxOpenPaneResult(value.browserPane) } : {}),
103
+ ...(stringField(value, "workspaceRef") ?? stringField(value, "ref")
104
+ ? { workspaceRef: stringField(value, "workspaceRef") ?? stringField(value, "ref") }
105
+ : {}),
88
106
  };
89
107
  }
90
108
 
91
- function parseCmuxOpenPaneResult(value: unknown): CmuxOpenPaneResult {
92
- if (!isRecord(value)) throw new Error(`cmux.open returned a non-object pane result`);
109
+ export function parseCmuxPaneResult(value: unknown): CmuxPaneResult {
110
+ if (!isRecord(value)) throw new Error(`cmux.call returned a non-object pane result`);
93
111
  return {
94
- ...optionalStringField(value, "paneId"),
95
- ...optionalStringField(value, "paneRef"),
96
- ...optionalStringField(value, "surfaceId"),
97
- ...optionalStringField(value, "surfaceRef"),
112
+ ...(stringField(value, "paneId") ?? stringField(value, "pane")
113
+ ? { paneId: stringField(value, "paneId") ?? stringField(value, "pane") }
114
+ : {}),
115
+ ...(stringField(value, "paneRef") ?? stringField(value, "paneRef")
116
+ ? { paneRef: stringField(value, "paneRef") ?? stringField(value, "paneRef") }
117
+ : {}),
118
+ ...(stringField(value, "surfaceId") ?? stringField(value, "surface")
119
+ ? { surfaceId: stringField(value, "surfaceId") ?? stringField(value, "surface") }
120
+ : {}),
121
+ ...(stringField(value, "surfaceRef") ?? stringField(value, "surfaceRef")
122
+ ? { surfaceRef: stringField(value, "surfaceRef") ?? stringField(value, "surfaceRef") }
123
+ : {}),
98
124
  };
99
125
  }
100
126
 
101
- function arrayField<Item>(
102
- record: Record<string, unknown>,
103
- key: string,
104
- parseItem: (value: unknown) => Item,
105
- ): Item[] {
106
- const value = record[key];
107
- if (value === undefined) return [];
108
- if (!Array.isArray(value)) throw new Error(`cmux.open result ${key} must be an array`);
109
- return value.map(parseItem);
110
- }
111
-
112
- function optionalStringField(record: Record<string, unknown>, key: string): Record<string, string> {
113
- const value = stringField(record, key);
114
- return value ? { [key]: value } : {};
115
- }
116
-
117
127
  function stringField(record: Record<string, unknown>, key: string): string | undefined {
118
128
  const value = record[key];
119
129
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const RIGKIT_PROVIDER_CMUX_VERSION = "0.2.13";
1
+ export const RIGKIT_PROVIDER_CMUX_VERSION = "0.2.14";