@rigkit/provider-cmux 0.2.4 → 0.2.5

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
@@ -52,11 +52,16 @@ export default workflow("site", {
52
52
  sshOptions: ["ServerAliveInterval=15"],
53
53
  },
54
54
  cwd: "/workspace/site",
55
- command: "pnpm dev",
55
+ surfaceLayout: "tabs",
56
+ terminals: [{ command: "pnpm dev" }],
56
57
  url: "http://localhost:3000",
57
58
  });
58
59
  },
59
60
  });
60
61
  ```
61
62
 
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
+
62
67
  Local hosts can import `@rigkit/provider-cmux/host` to register the trusted `cmux.open` 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.4",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,8 +17,8 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@rigkit/sdk": "0.2.4",
21
- "@rigkit/engine": "0.2.4"
20
+ "@rigkit/sdk": "0.2.5",
21
+ "@rigkit/engine": "0.2.5"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/bun": "latest",
@@ -1,7 +1,7 @@
1
1
  export const CMUX_OPEN_CAPABILITY_ID = "cmux.open";
2
2
 
3
3
  export const CMUX_OPEN_SCHEMA_HASH =
4
- "sha256:671373232fc79a7f75dd01c8c83c0c350af62b349a89bb3cfcc96af2cd76c878";
4
+ "sha256:3a2975cfe53089c6a607da751b55575dc8806bd90132242d4b7e9065a26ae3af";
5
5
 
6
6
  export const CMUX_OPEN_CAPABILITY = {
7
7
  id: CMUX_OPEN_CAPABILITY_ID,
@@ -29,24 +29,40 @@ export type CmuxRemoteReadyOptions = {
29
29
  requireProxy?: boolean;
30
30
  };
31
31
 
32
+ export type CmuxOpenTerminalDirection = "left" | "right" | "up" | "down";
33
+ export type CmuxOpenSurfaceLayout = "splits" | "tabs";
34
+
35
+ export type CmuxOpenTerminalInput = {
36
+ command: string;
37
+ cwd?: string;
38
+ direction?: CmuxOpenTerminalDirection;
39
+ focus?: boolean;
40
+ };
41
+
32
42
  export type CmuxOpenInput = {
33
43
  name: string;
34
44
  ssh?: CmuxOpenSshInput;
35
45
  cwd?: string;
36
- command?: string;
46
+ surfaceLayout?: CmuxOpenSurfaceLayout;
47
+ terminals?: readonly CmuxOpenTerminalInput[];
37
48
  url?: string;
38
49
  focus?: boolean;
39
50
  waitForRemoteReady?: boolean | CmuxRemoteReadyOptions;
40
51
  };
41
52
 
53
+ export type CmuxOpenPaneResult = {
54
+ paneId?: string;
55
+ paneRef?: string;
56
+ surfaceId?: string;
57
+ surfaceRef?: string;
58
+ };
59
+
42
60
  export type CmuxOpenResult = {
43
61
  sessionId: string;
44
62
  workspaceId: string;
45
63
  workspaceRef?: string;
46
- terminalPaneId?: string;
47
- terminalSurfaceId?: string;
48
- browserPaneId?: string;
49
- browserSurfaceId?: string;
64
+ terminalPanes: CmuxOpenPaneResult[];
65
+ browserPane?: CmuxOpenPaneResult;
50
66
  };
51
67
 
52
68
  export type CmuxOpenSession = CmuxOpenResult & {
package/src/host.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import {
2
2
  createCmuxClient,
3
3
  formatShellCommand,
4
- type CmuxBrowserOpenOptions,
5
4
  type CmuxClient,
6
5
  type CmuxClientOptions,
7
- type CmuxNewPaneOptions,
8
6
  type CmuxNewWorkspaceOptions,
9
7
  type CmuxPane,
10
8
  type CmuxPortsKickOptions,
@@ -21,8 +19,11 @@ import {
21
19
  import {
22
20
  CMUX_OPEN_CAPABILITY,
23
21
  type CmuxOpenInput,
22
+ type CmuxOpenPaneResult,
24
23
  type CmuxOpenResult,
25
24
  type CmuxOpenSshInput,
25
+ type CmuxOpenTerminalDirection,
26
+ type CmuxOpenTerminalInput,
26
27
  type CmuxRemoteReadyOptions,
27
28
  } from "./capabilities.ts";
28
29
 
@@ -33,6 +34,7 @@ export type CmuxOpenClient = Pick<
33
34
  | "newWorkspace"
34
35
  | "ssh"
35
36
  | "newPane"
37
+ | "newSurface"
36
38
  | "send"
37
39
  | "portsKick"
38
40
  | "browserOpen"
@@ -72,9 +74,8 @@ export async function openCmux(
72
74
  ...(options.logger ? { logger: options.logger } : {}),
73
75
  printCommands: options.clientOptions?.printCommands ?? false,
74
76
  });
75
- const command = commandForInput(input);
76
77
  let workspace: CmuxWorkspace;
77
- let terminalPane: CmuxPane | undefined;
78
+ const terminalPanes: CmuxPane[] = [];
78
79
 
79
80
  logger?.(`cmux: opening ${input.name}`);
80
81
  if (input.ssh) {
@@ -89,27 +90,34 @@ export async function openCmux(
89
90
  const workspaceOptions: CmuxNewWorkspaceOptions = {
90
91
  name: input.name,
91
92
  cwd: input.cwd,
92
- command: input.command,
93
93
  focus: input.focus,
94
94
  };
95
95
  workspace = await cmux.newWorkspace(workspaceOptions);
96
96
  }
97
97
 
98
98
  const workspaceId = workspace.id ?? workspace.handle;
99
+ const useTabLayout = input.surfaceLayout === "tabs";
99
100
 
100
- if (input.ssh && command) {
101
- logger?.(input.cwd ? `cmux: starting command in ${input.cwd}` : "cmux: starting command");
102
- const paneOptions: CmuxNewPaneOptions = {
103
- workspace: workspaceId,
104
- type: "terminal",
105
- direction: "down",
106
- focus: true,
107
- };
108
- terminalPane = await cmux.newPane(paneOptions);
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);
109
117
  const sendOptions: CmuxSendOptions = {
110
118
  workspace: workspaceId,
111
119
  surface: terminalPane.surface,
112
- text: command,
120
+ text: commandForTerminal(terminal, input),
113
121
  };
114
122
  await cmux.send(sendOptions);
115
123
  }
@@ -120,25 +128,34 @@ export async function openCmux(
120
128
  await cmux.waitForRemoteReady(workspaceId, waitOptions);
121
129
  }
122
130
 
123
- if (input.ssh && terminalPane?.surface) {
131
+ if (input.ssh && terminalPanes.some((pane) => pane.surface)) {
124
132
  logger?.("cmux: refreshing remote ports");
125
- const kickOptions: CmuxPortsKickOptions = {
126
- workspace: workspaceId,
127
- surface: terminalPane.surface,
128
- reason: "command",
129
- };
130
- await cmux.portsKick(kickOptions);
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);
141
+ }
131
142
  }
132
143
 
133
144
  let browserPane: CmuxPane | undefined;
134
145
  if (input.url) {
135
146
  logger?.(`cmux: opening ${input.url}`);
136
- const browserOptions: CmuxBrowserOpenOptions = {
137
- workspace: workspaceId,
138
- url: input.url,
139
- focus: input.focus !== false,
140
- };
141
- browserPane = await cmux.browserOpen(browserOptions);
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
+ });
142
159
  }
143
160
 
144
161
  if (input.focus !== false) {
@@ -151,10 +168,8 @@ export async function openCmux(
151
168
  sessionId: workspaceId,
152
169
  workspaceId,
153
170
  ...(workspace.ref ? { workspaceRef: workspace.ref } : {}),
154
- ...(terminalPane?.pane ? { terminalPaneId: terminalPane.pane } : {}),
155
- ...(terminalPane?.surface ? { terminalSurfaceId: terminalPane.surface } : {}),
156
- ...(browserPane?.pane ? { browserPaneId: browserPane.pane } : {}),
157
- ...(browserPane?.surface ? { browserSurfaceId: browserPane.surface } : {}),
171
+ terminalPanes: terminalPanes.map(paneResultForCmuxPane),
172
+ ...(browserPane ? { browserPane: paneResultForCmuxPane(browserPane) } : {}),
158
173
  };
159
174
  }
160
175
 
@@ -174,7 +189,8 @@ export function parseCmuxOpenInput(value: unknown): CmuxOpenInput {
174
189
  name,
175
190
  ...(value.ssh !== undefined ? { ssh: parseSshInput(value.ssh) } : {}),
176
191
  ...optionalStringField(value, "cwd"),
177
- ...optionalStringField(value, "command"),
192
+ ...optionalSurfaceLayoutField(value, "surfaceLayout"),
193
+ ...(value.terminals !== undefined ? { terminals: parseTerminalInputs(value.terminals) } : {}),
178
194
  ...optionalStringField(value, "url"),
179
195
  ...optionalBooleanField(value, "focus"),
180
196
  ...(value.waitForRemoteReady !== undefined
@@ -237,10 +253,34 @@ function sshDestination(ssh: Extract<CmuxOpenSshInput, object>): string {
237
253
  return `${ssh.username}@${ssh.host}`;
238
254
  }
239
255
 
240
- function commandForInput(input: CmuxOpenInput): string | undefined {
241
- if (!input.command) return undefined;
242
- const prefix = input.cwd ? `${formatShellCommand(["cd", input.cwd])} && ` : "";
243
- return `${prefix}${input.command}\n`;
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
+ };
244
284
  }
245
285
 
246
286
  function remoteReadyOptionsForInput(input: CmuxOpenInput): CmuxWaitForRemoteOptions | false {
@@ -301,6 +341,30 @@ function optionalBooleanField(record: Record<string, unknown>, key: string): Rec
301
341
  return { [key]: value };
302
342
  }
303
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
+ }
365
+ return { [key]: value };
366
+ }
367
+
304
368
  function isRecord(value: unknown): value is Record<string, unknown> {
305
369
  return typeof value === "object" && value !== null && !Array.isArray(value);
306
370
  }
package/src/index.test.ts CHANGED
@@ -135,7 +135,7 @@ describe("cmux sdk", () => {
135
135
  ]);
136
136
  });
137
137
 
138
- test("creates panes, opens browsers, and sends terminal text", async () => {
138
+ test("creates panes, surfaces, opens browsers, and sends terminal text", async () => {
139
139
  const calls: Array<{ method: string; params: CmuxRpcParams }> = [];
140
140
  const cmux = createCmuxClient({
141
141
  printCommands: false,
@@ -161,6 +161,16 @@ describe("cmux sdk", () => {
161
161
  pane_ref: "pane:11",
162
162
  };
163
163
  }
164
+ if (method === "surface.create") {
165
+ return {
166
+ workspace_id: "00000000-0000-0000-0000-000000000009",
167
+ workspace_ref: "workspace:9",
168
+ surface_id: "00000000-0000-0000-0000-000000000012",
169
+ surface_ref: "surface:12",
170
+ pane_id: "00000000-0000-0000-0000-000000000008",
171
+ pane_ref: "pane:8",
172
+ };
173
+ }
164
174
  return {};
165
175
  },
166
176
  });
@@ -181,6 +191,17 @@ describe("cmux sdk", () => {
181
191
  surface: pane.surface,
182
192
  reason: "refresh",
183
193
  });
194
+ const surface = await cmux.newSurface({
195
+ workspace: "00000000-0000-0000-0000-000000000009",
196
+ pane: pane.pane,
197
+ type: "terminal",
198
+ focus: false,
199
+ });
200
+ await cmux.send({
201
+ workspace: "00000000-0000-0000-0000-000000000009",
202
+ surface: surface.surface,
203
+ text: "codex\\n",
204
+ });
184
205
  await cmux.browserOpen({
185
206
  workspace: "00000000-0000-0000-0000-000000000009",
186
207
  url: "http://localhost:3000",
@@ -188,6 +209,7 @@ describe("cmux sdk", () => {
188
209
  });
189
210
 
190
211
  expect(pane.surface).toBe("00000000-0000-0000-0000-000000000007");
212
+ expect(surface.surface).toBe("00000000-0000-0000-0000-000000000012");
191
213
  expect(calls).toEqual([
192
214
  {
193
215
  method: "pane.create",
@@ -214,6 +236,23 @@ describe("cmux sdk", () => {
214
236
  reason: "refresh",
215
237
  },
216
238
  },
239
+ {
240
+ method: "surface.create",
241
+ params: {
242
+ type: "terminal",
243
+ pane_id: "00000000-0000-0000-0000-000000000008",
244
+ workspace_id: "00000000-0000-0000-0000-000000000009",
245
+ focus: false,
246
+ },
247
+ },
248
+ {
249
+ method: "surface.send_text",
250
+ params: {
251
+ workspace_id: "00000000-0000-0000-0000-000000000009",
252
+ surface_id: "00000000-0000-0000-0000-000000000012",
253
+ text: "codex\\n",
254
+ },
255
+ },
217
256
  {
218
257
  method: "browser.open_split",
219
258
  params: {
@@ -400,7 +439,7 @@ describe("cmux sdk", () => {
400
439
  expect(() => cmux.run(["bad"])).toThrow(CmuxCommandError);
401
440
  });
402
441
 
403
- test("handles cmux.open host capability for an ssh workspace", async () => {
442
+ test("handles cmux.open host capability for an ssh workspace with tabs", async () => {
404
443
  const calls: Array<{ method: string; params: unknown }> = [];
405
444
  const logs: string[] = [];
406
445
  const client = fakeOpenClient(calls);
@@ -413,7 +452,11 @@ describe("cmux sdk", () => {
413
452
  sshOptions: ["ServerAliveInterval=15"],
414
453
  },
415
454
  cwd: "/workspace/site",
416
- command: "pnpm dev",
455
+ surfaceLayout: "tabs",
456
+ terminals: [
457
+ { command: "pnpm dev" },
458
+ { command: "codex", focus: false },
459
+ ],
417
460
  url: "http://localhost:4321",
418
461
  }, { client, logger: (message) => logs.push(message) });
419
462
 
@@ -421,10 +464,26 @@ describe("cmux sdk", () => {
421
464
  sessionId: "workspace-1",
422
465
  workspaceId: "workspace-1",
423
466
  workspaceRef: "workspace:1",
424
- terminalPaneId: "pane-1",
425
- terminalSurfaceId: "surface-1",
426
- browserPaneId: "pane-2",
427
- browserSurfaceId: "surface-2",
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",
486
+ },
428
487
  });
429
488
  expect(calls).toEqual([
430
489
  {
@@ -436,11 +495,10 @@ describe("cmux sdk", () => {
436
495
  }),
437
496
  },
438
497
  {
439
- method: "newPane",
498
+ method: "newSurface",
440
499
  params: {
441
500
  workspace: "workspace-1",
442
501
  type: "terminal",
443
- direction: "down",
444
502
  focus: true,
445
503
  },
446
504
  },
@@ -452,6 +510,22 @@ describe("cmux sdk", () => {
452
510
  text: "cd /workspace/site && pnpm dev\n",
453
511
  },
454
512
  },
513
+ {
514
+ method: "newSurface",
515
+ params: {
516
+ workspace: "workspace-1",
517
+ type: "terminal",
518
+ focus: false,
519
+ },
520
+ },
521
+ {
522
+ method: "send",
523
+ params: {
524
+ workspace: "workspace-1",
525
+ surface: "surface-2",
526
+ text: "cd /workspace/site && codex\n",
527
+ },
528
+ },
455
529
  {
456
530
  method: "waitForRemoteReady",
457
531
  params: {
@@ -468,9 +542,18 @@ describe("cmux sdk", () => {
468
542
  },
469
543
  },
470
544
  {
471
- method: "browserOpen",
545
+ method: "portsKick",
546
+ params: {
547
+ workspace: "workspace-1",
548
+ surface: "surface-2",
549
+ reason: "command",
550
+ },
551
+ },
552
+ {
553
+ method: "newSurface",
472
554
  params: {
473
555
  workspace: "workspace-1",
556
+ type: "browser",
474
557
  url: "http://localhost:4321",
475
558
  focus: true,
476
559
  },
@@ -484,7 +567,8 @@ describe("cmux sdk", () => {
484
567
  expect(logs).toEqual([
485
568
  "cmux: opening website",
486
569
  "cmux: connecting remote workspace",
487
- "cmux: starting command in /workspace/site",
570
+ "cmux: starting terminal in /workspace/site",
571
+ "cmux: starting terminal in /workspace/site",
488
572
  "cmux: waiting for remote ports",
489
573
  "cmux: refreshing remote ports",
490
574
  "cmux: opening http://localhost:4321",
@@ -611,6 +695,7 @@ describe("cmux sdk", () => {
611
695
  });
612
696
 
613
697
  function fakeOpenClient(calls: Array<{ method: string; params: unknown }>): CmuxOpenClient {
698
+ let terminalPaneIndex = 0;
614
699
  return {
615
700
  async newWorkspace(params) {
616
701
  calls.push({ method: "newWorkspace", params });
@@ -622,13 +707,26 @@ function fakeOpenClient(calls: Array<{ method: string; params: unknown }>): Cmux
622
707
  },
623
708
  async newPane(params) {
624
709
  calls.push({ method: "newPane", params });
710
+ terminalPaneIndex += 1;
711
+ return {
712
+ workspace: "workspace-1",
713
+ workspaceRef: "workspace:1",
714
+ pane: `pane-${terminalPaneIndex}`,
715
+ paneRef: `pane:${terminalPaneIndex}`,
716
+ surface: `surface-${terminalPaneIndex}`,
717
+ surfaceRef: `surface:${terminalPaneIndex}`,
718
+ };
719
+ },
720
+ async newSurface(params) {
721
+ calls.push({ method: "newSurface", params });
722
+ terminalPaneIndex += 1;
625
723
  return {
626
724
  workspace: "workspace-1",
627
725
  workspaceRef: "workspace:1",
628
- pane: "pane-1",
629
- paneRef: "pane:1",
630
- surface: "surface-1",
631
- surfaceRef: "surface:1",
726
+ pane: `pane-${terminalPaneIndex}`,
727
+ paneRef: `pane:${terminalPaneIndex}`,
728
+ surface: `surface-${terminalPaneIndex}`,
729
+ surfaceRef: `surface:${terminalPaneIndex}`,
632
730
  };
633
731
  },
634
732
  async send(params) {
@@ -644,10 +742,10 @@ function fakeOpenClient(calls: Array<{ method: string; params: unknown }>): Cmux
644
742
  return {
645
743
  workspace: "workspace-1",
646
744
  workspaceRef: "workspace:1",
647
- pane: "pane-2",
648
- paneRef: "pane:2",
649
- surface: "surface-2",
650
- surfaceRef: "surface:2",
745
+ pane: "browser-pane-1",
746
+ paneRef: "pane:browser-1",
747
+ surface: "browser-surface-1",
748
+ surfaceRef: "surface:browser-1",
651
749
  };
652
750
  },
653
751
  async selectWorkspace(workspace) {
package/src/index.ts CHANGED
@@ -103,6 +103,14 @@ export type CmuxNewPaneOptions = {
103
103
  focus?: boolean;
104
104
  };
105
105
 
106
+ export type CmuxNewSurfaceOptions = {
107
+ workspace?: string;
108
+ pane?: string;
109
+ type?: "terminal" | "browser";
110
+ url?: string;
111
+ focus?: boolean;
112
+ };
113
+
106
114
  export type CmuxPane = {
107
115
  workspace?: string;
108
116
  workspaceRef?: string;
@@ -277,6 +285,17 @@ export class CmuxClient {
277
285
  return paneFromResult(await this.rpc("pane.create", params));
278
286
  }
279
287
 
288
+ async newSurface(options: CmuxNewSurfaceOptions = {}): Promise<CmuxPane> {
289
+ const params: CmuxRpcParams = {};
290
+ if (options.type) params.type = options.type;
291
+ if (options.pane) params.pane_id = options.pane;
292
+ if (options.workspace) params.workspace_id = options.workspace;
293
+ if (options.url) params.url = options.url;
294
+ if (options.focus !== undefined) params.focus = options.focus;
295
+
296
+ return paneFromResult(await this.rpc("surface.create", params));
297
+ }
298
+
280
299
  async listWorkspaces(): Promise<CmuxWorkspaceStatus[]> {
281
300
  const result = await this.rpc("workspace.list");
282
301
  const workspaces = Array.isArray(result.workspaces)
@@ -477,9 +496,13 @@ export {
477
496
  CMUX_OPEN_CAPABILITY_ID,
478
497
  CMUX_OPEN_SCHEMA_HASH,
479
498
  type CmuxOpenInput,
499
+ type CmuxOpenPaneResult,
480
500
  type CmuxOpenResult,
481
501
  type CmuxOpenSession,
502
+ type CmuxOpenSurfaceLayout,
482
503
  type CmuxOpenSshInput,
504
+ type CmuxOpenTerminalDirection,
505
+ type CmuxOpenTerminalInput,
483
506
  type CmuxRemoteReadyOptions,
484
507
  } from "./capabilities.ts";
485
508
  export {
package/src/provider.ts CHANGED
@@ -7,6 +7,7 @@ import type { BaseProviderPlugin, WorkflowProviderController } from "@rigkit/eng
7
7
  import {
8
8
  CMUX_OPEN_CAPABILITY_ID,
9
9
  type CmuxOpenInput,
10
+ type CmuxOpenPaneResult,
10
11
  type CmuxOpenResult,
11
12
  type CmuxOpenSession,
12
13
  } from "./capabilities.ts";
@@ -82,13 +83,32 @@ export function parseCmuxOpenResult(value: unknown): CmuxOpenResult {
82
83
  sessionId,
83
84
  workspaceId,
84
85
  ...optionalStringField(value, "workspaceRef"),
85
- ...optionalStringField(value, "terminalPaneId"),
86
- ...optionalStringField(value, "terminalSurfaceId"),
87
- ...optionalStringField(value, "browserPaneId"),
88
- ...optionalStringField(value, "browserSurfaceId"),
86
+ terminalPanes: arrayField(value, "terminalPanes", parseCmuxOpenPaneResult),
87
+ ...(value.browserPane !== undefined ? { browserPane: parseCmuxOpenPaneResult(value.browserPane) } : {}),
89
88
  };
90
89
  }
91
90
 
91
+ function parseCmuxOpenPaneResult(value: unknown): CmuxOpenPaneResult {
92
+ if (!isRecord(value)) throw new Error(`cmux.open returned a non-object pane result`);
93
+ return {
94
+ ...optionalStringField(value, "paneId"),
95
+ ...optionalStringField(value, "paneRef"),
96
+ ...optionalStringField(value, "surfaceId"),
97
+ ...optionalStringField(value, "surfaceRef"),
98
+ };
99
+ }
100
+
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
+
92
112
  function optionalStringField(record: Record<string, unknown>, key: string): Record<string, string> {
93
113
  const value = stringField(record, key);
94
114
  return value ? { [key]: value } : {};
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const RIGKIT_PROVIDER_CMUX_VERSION = "0.2.4";
1
+ export const RIGKIT_PROVIDER_CMUX_VERSION = "0.2.5";