# libtmux for OCaml

Source: https://libtmux.org/en/ocaml/latest/

> libtmux for OCaml, version latest.

![](https://libtmux.org/en/brand/ocaml/library/logo.svg)

OCaml-based tmux client

tmux client library for OCaml. Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents

Product

Core API Workspace Manager

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let capture_graph client (initial : Created.t) spawn =
  let linked =
    get
      (Client.create_session client ~name:"linked" ~window_name:"linked-home"
         ~spawn ())
  in
  let pane =
    Snapshot.find_pane
      (get (Client.capture client))
      (Pane.Target.id linked.pane)
    |> Option.get
  in
  let text = "showcase ready" in
  ignore (get (Client.wait_for_text ~timeout:0.5 client pane ~text));
  get
    (Client.link_window client initial.window ~session:linked.session ~index:7);
  get (Client.capture client)


let print_graph snapshot =
  Snapshot.sessions snapshot
  |> Iarray.iter (fun session ->
      Session.window_links session
      |> Iarray.iter (fun link ->
          let window = Window_link.window link in
          Printf.printf "%s/%d %s: %d pane(s)\n" (Session.name session)
            (Window_link.index link) (Window.name window)
            (Iarray.length (Window.panes window))));
  print_endline "graph: 2 sessions, 2 windows, 3 links, 2 panes"


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  let snapshot = with_demo binary capture_graph in
  print_graph snapshot
```

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let capture_graph client (initial : Created.t) spawn =
  let linked =
    get
      (Client.create_session client ~name:"linked" ~window_name:"linked-home"
         ~spawn ())
  in
  let pane =
    Snapshot.find_pane
      (get (Client.capture client))
      (Pane.Target.id linked.pane)
    |> Option.get
  in
  let text = "showcase ready" in
  ignore (get (Client.wait_for_text ~timeout:0.5 client pane ~text));
  get
    (Client.link_window client initial.window ~session:linked.session ~index:7);
  get (Client.capture client)


let print_graph snapshot =
  Snapshot.sessions snapshot
  |> Iarray.iter (fun session ->
      Session.window_links session
      |> Iarray.iter (fun link ->
          let window = Window_link.window link in
          Printf.printf "%s/%d %s: %d pane(s)\n" (Session.name session)
            (Window_link.index link) (Window.name window)
            (Iarray.length (Window.panes window))));
  print_endline "graph: 2 sessions, 2 windows, 3 links, 2 panes"


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  let snapshot = with_demo binary capture_graph in
  print_graph snapshot
```

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let capture_graph client (initial : Created.t) spawn =
  let linked =
    get
      (Client.create_session client ~name:"linked" ~window_name:"linked-home"
         ~spawn ())
  in
  let pane =
    Snapshot.find_pane
      (get (Client.capture client))
      (Pane.Target.id linked.pane)
    |> Option.get
  in
  let text = "showcase ready" in
  ignore (get (Client.wait_for_text ~timeout:0.5 client pane ~text));
  get
    (Client.link_window client initial.window ~session:linked.session ~index:7);
  get (Client.capture client)


let print_graph snapshot =
  Snapshot.sessions snapshot
  |> Iarray.iter (fun session ->
      Session.window_links session
      |> Iarray.iter (fun link ->
          let window = Window_link.window link in
          Printf.printf "%s/%d %s: %d pane(s)\n" (Session.name session)
            (Window_link.index link) (Window.name window)
            (Iarray.length (Window.panes window))));
  assert (Iarray.length (Snapshot.sessions snapshot) = 2);
  assert (Iarray.length (Snapshot.windows snapshot) = 2);
  assert (Iarray.length (Snapshot.window_links snapshot) = 3);
  assert (Iarray.length (Snapshot.panes snapshot) = 2);
  print_endline "graph: 2 sessions, 2 windows, 3 links, 2 panes"


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  let snapshot = with_demo binary capture_graph in
  print_graph snapshot
```

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let capture_graph client (initial : Created.t) spawn =
  let linked =
    get
      (Client.create_session client ~name:"linked" ~window_name:"linked-home"
         ~spawn ())
  in
  let pane =
    Snapshot.find_pane
      (get (Client.capture client))
      (Pane.Target.id linked.pane)
    |> Option.get
  in
  let text = "showcase ready" in
  ignore (get (Client.wait_for_text ~timeout:0.5 client pane ~text));
  get
    (Client.link_window client initial.window ~session:linked.session ~index:7);
  get (Client.capture client)


let print_graph snapshot =
  Snapshot.sessions snapshot
  |> Iarray.iter (fun session ->
      Session.window_links session
      |> Iarray.iter (fun link ->
          let window = Window_link.window link in
          Printf.printf "%s/%d %s: %d pane(s)\n" (Session.name session)
            (Window_link.index link) (Window.name window)
            (Iarray.length (Window.panes window))));
  assert (Iarray.length (Snapshot.sessions snapshot) = 2);
  assert (Iarray.length (Snapshot.windows snapshot) = 2);
  assert (Iarray.length (Snapshot.window_links snapshot) = 3);
  assert (Iarray.length (Snapshot.panes snapshot) = 2);
  print_endline "graph: 2 sessions, 2 windows, 3 links, 2 panes"


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  let snapshot = with_demo binary capture_graph in
  print_graph snapshot
```

Example details

\+ Error handling + Cleanup

Run this example

OCaml 5.5.2, Dune 3.24.2, Linux and tmux 3.3a through 3.8. Install libtmux, libtmux-workspace and libtmux-unix in the active opam switch, then save these files at their shown paths.

Save the program above as `examples/showcase_graph.ml` in a new directory, along with the files below.

Every view retains protected cleanup. A private exit-unattached configuration ends the owned daemon after its clients close. The program stops its private tmux server and removes any temporary files it created when it finishes. Every view already unwraps result values and retains protected cleanup. The detailed view also checks the captured graph cardinalities.

dune-project

```lisp
(lang dune 3.24)
```

examples/dune

```lisp
(executable
 (name showcase_graph)
 (libraries libtmux libtmux-workspace libtmux-unix unix))
```

### Setup and run

```bash
opam exec -- dune exec examples/showcase_graph.exe
```

### Expected output

```text
graph: 2 sessions, 2 windows, 3 links, 2 panes
```

APIs used

* [`Client.create_session`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-create_session/)
* [`Client.capture`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-capture/)
* [`Client.link_window`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-link_window/)
* [`Window_link.window`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window_link-window/)
* [`Snapshot.sessions`](https://libtmux.org/en/ocaml/latest/reference/libtmux-snapshot-sessions/)
* [`Session.window_links`](https://libtmux.org/en/ocaml/latest/reference/libtmux-session-window_links/)
* [`Window.panes`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window-panes/)

examples/showcase_graph.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let capture_graph client (initial : Created.t) spawn =
  let linked =
    get
      (Client.create_session client ~name:"linked" ~window_name:"linked-home"
         ~spawn ())
  in
  let pane =
    Snapshot.find_pane
      (get (Client.capture client))
      (Pane.Target.id linked.pane)
    |> Option.get
  in
  let text = "showcase ready" in
  ignore (get (Client.wait_for_text ~timeout:0.5 client pane ~text));
  get
    (Client.link_window client initial.window ~session:linked.session ~index:7);
  get (Client.capture client)


let print_graph snapshot =
  Snapshot.sessions snapshot
  |> Iarray.iter (fun session ->
      Session.window_links session
      |> Iarray.iter (fun link ->
          let window = Window_link.window link in
          Printf.printf "%s/%d %s: %d pane(s)\n" (Session.name session)
            (Window_link.index link) (Window.name window)
            (Iarray.length (Window.panes window))));
  assert (Iarray.length (Snapshot.sessions snapshot) = 2);
  assert (Iarray.length (Snapshot.windows snapshot) = 2);
  assert (Iarray.length (Snapshot.window_links snapshot) = 3);
  assert (Iarray.length (Snapshot.panes snapshot) = 2);
  print_endline "graph: 2 sessions, 2 windows, 3 links, 2 panes"


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  let snapshot = with_demo binary capture_graph in
  print_graph snapshot
```

APIs used

* [`Client.create_session`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-create_session/)
* [`Client.capture`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-capture/)
* [`Client.link_window`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-link_window/)
* [`Window_link.window`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window_link-window/)
* [`Snapshot.sessions`](https://libtmux.org/en/ocaml/latest/reference/libtmux-snapshot-sessions/)
* [`Session.window_links`](https://libtmux.org/en/ocaml/latest/reference/libtmux-session-window_links/)
* [`Window.panes`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window-panes/)

tmux 3.3+ The whole program, [`examples/showcase_graph.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_graph.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

examples/showcase_watch.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let watch_output binary blocking (initial : Created.t) _spawn =
  Eio_main.run @@ fun env ->
  Eio.Switch.run @@ fun sw ->
  let module Client = Libtmux_eio.Client in
  let module Subscription = Libtmux_eio.Subscription in
  let client =
    get
      (Client.connect ~sw ~binary
         ~process_mgr:(Eio.Stdenv.process_mgr env)
         ~mono_clock:(Eio.Stdenv.mono_clock env)
         (Source.endpoint (Libtmux_unix.Client.source blocking)))
  in
  Fun.protect
    ~finally:(fun () ->
      Eio.Cancel.protect (fun () -> get (Client.close client)))
    (fun () ->
      let pane =
        Snapshot.find_pane
          (get (Client.capture client))
          (Pane.Target.id initial.pane)
        |> Option.get
      in
      let subscription =
        get (Client.subscribe ~capacity_bytes:4096 client pane)
      in
      Fun.protect
        ~finally:(fun () ->
          Eio.Cancel.protect (fun () -> get (Subscription.close subscription)))
        (fun () ->
          let cursor, () =
            Eio.Fiber.pair
              (fun () ->
                match
                  Subscription.wait_for_bytes ~timeout:0.5 subscription
                    ~text:"changed"
                with
                | Ok cursor -> cursor
                | Error (Output.Failure error) ->
                    failwith (Format.asprintf "%a" Error.pp error)
                | Error (Output.Ended reason) ->
                    failwith (Format.asprintf "%a" Output.reason_pp reason))
              (fun () ->
                get (Client.send_text client (Pane.target pane) ~text:"changed");
                get (Client.send_keys client (Pane.target pane) [ Key.Enter ]))
          in
          assert (cursor.offset >= 7L);
          print_endline "observed: changed"))


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  with_demo binary (watch_output binary)
```

APIs used

* [`Client.connect`](https://libtmux.org/en/ocaml/latest/reference/libtmux_eio-client-connect/)
* [`Client.subscribe`](https://libtmux.org/en/ocaml/latest/reference/libtmux_eio-client-subscribe/)
* [`Subscription.wait_for_bytes`](https://libtmux.org/en/ocaml/latest/reference/libtmux_eio-subscription-wait_for_bytes/)
* [`Client.send_text`](https://libtmux.org/en/ocaml/latest/reference/libtmux_eio-client-send_text/)

tmux 3.3+ The whole program, [`examples/showcase_watch.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_watch.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

examples/showcase_send_keys.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let send_keys client (initial : Created.t) _spawn =
  let text = "literal: $HOME; #{pane_id}" in
  let pane =
    Snapshot.find_pane
      (get (Client.capture client))
      (Pane.Target.id initial.pane)
    |> Option.get
  in
  get (Client.send_text client (Pane.target pane) ~text);
  get (Client.send_keys client (Pane.target pane) [ Key.Enter ]);
  ignore (get (Client.wait_for_text ~timeout:0.5 client pane ~text));
  Printf.printf "sent: %s\n" text


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  with_demo binary send_keys
```

APIs used

* [`Client.send_text`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-send_text/)
* [`Client.send_keys`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-send_keys/)
* [`Client.wait_for_text`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-wait_for_text/)

tmux 3.3+ The whole program, [`examples/showcase_send_keys.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_send_keys.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

examples/showcase_rename_window\.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let rename_window client (initial : Created.t) _spawn =
  let before =
    Snapshot.find_window
      (get (Client.capture client))
      (Window.Target.id initial.window)
    |> Option.get
  in
  get (Client.rename_window client initial.window ~name:"editor-main");
  let after =
    Snapshot.find_window (get (Client.capture client)) (Window.id before)
    |> Option.get
  in
  assert (Window.name before = "editor");
  assert (Window.name after = "editor-main");
  Printf.printf "renamed: %s\n" (Window.name after)


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  with_demo binary rename_window
```

APIs used

* [`Snapshot.find_window`](https://libtmux.org/en/ocaml/latest/reference/libtmux-snapshot-find_window/)
* [`Client.rename_window`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-rename_window/)
* [`Window.name`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window-name/)

tmux 3.3+ The whole program, [`examples/showcase_rename_window.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_rename_window.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

examples/showcase_create_window\.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let create_window client (initial : Created.t) spawn =
  let created =
    get (Client.create_window client initial.session ~name:"logs" ~spawn)
  in
  let window =
    Snapshot.find_window
      (get (Client.capture client))
      (Window.Target.id created.window)
    |> Option.get
  in
  assert (Window.name window = "logs");
  Printf.printf "created: %s\n" (Window.name window)


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  with_demo binary create_window
```

APIs used

* [`Client.create_window`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-create_window/)
* [`Snapshot.find_window`](https://libtmux.org/en/ocaml/latest/reference/libtmux-snapshot-find_window/)

tmux 3.3+ The whole program, [`examples/showcase_create_window.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_create_window.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

examples/showcase_move_window\.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let move_window client (initial : Created.t) spawn =
  let moving =
    get (Client.create_window client initial.session ~name:"build" ~spawn)
  in
  let destination =
    get
      (Client.create_session client ~name:"destination" ~window_name:"landing"
         ~spawn ())
  in
  get
    (Client.move_window_at client initial.session
       ~from_index:moving.window_index ~session:destination.session ~index:7);
  let window =
    Snapshot.find_window
      (get (Client.capture client))
      (Window.Target.id moving.window)
    |> Option.get
  in
  let link =
    Query.exactly_one (Iarray.to_seq (Window.links window)) |> Result.get_ok
  in
  assert (Window_link.index link = 7);
  assert (
    Session.Id.equal
      (Session.id (Window_link.session link))
      (Session.Target.id destination.session));
  Printf.printf "moved: %s at %d\n" (Window.name window)
    (Window_link.index link)


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  with_demo binary move_window
```

APIs used

* [`Client.move_window_at`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-move_window_at/)
* [`Query.exactly_one`](https://libtmux.org/en/ocaml/latest/reference/libtmux-query-exactly_one/)
* [`Window.links`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window-links/)
* [`Window_link.index`](https://libtmux.org/en/ocaml/latest/reference/libtmux-window_link-index/)

tmux 3.3+ The whole program, [`examples/showcase_move_window.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_move_window.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

examples/showcase_layout.ml

```ocaml
open Libtmux
module Client = Libtmux_unix.Client
module Workspace = Libtmux_unix.Workspace
module Config = Libtmux_workspace.Config
module Plan = Libtmux_workspace.Plan
module Execution = Libtmux_workspace.Execution


let get = function
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Error.pp error)


let set_layout client (initial : Created.t) spawn =
  ignore
    (get
       (Client.split_pane client initial.pane ~direction:Geometry.Right ~spawn));
  ignore
    (get
       (Client.split_pane client initial.pane ~direction:Geometry.Right ~spawn));
  get (Client.select_layout client initial.window Layout.Even_horizontal);
  let window =
    Snapshot.find_window
      (get (Client.capture client))
      (Window.Target.id initial.window)
    |> Option.get
  in
  let panes = Window.panes window in
  assert (Iarray.length panes = 3);
  assert (
    Iarray.for_all
      (fun pane -> Pane.top pane = 0 && Pane.height pane = Window.height window)
      panes);
  assert (
    Iarray.fold_left (fun width pane -> width + Pane.width pane) 2 panes
    = Window.width window);
  print_endline "layout: 3 horizontal panes"


let with_demo binary use =
  let directory = Filename.temp_dir "libtmux-ocaml-showcase-" "" in
  Fun.protect
    ~finally:(fun () ->
      Sys.readdir directory
      |> Array.iter (fun name -> Sys.remove (Filename.concat directory name));
      Unix.rmdir directory)
    (fun () ->
      let socket = Filename.concat directory "server" in
      (* Give this private daemon a lifetime even if startup fails. *)
      let config_file = Filename.concat directory "tmux.conf" in
      Out_channel.with_open_text config_file (fun output ->
          output_string output "set-option -s exit-unattached on\n");
      let wrapper = Filename.concat directory "tmux" in
      Out_channel.with_open_text wrapper (fun output ->
          Printf.fprintf output "#!/bin/sh\nexec %s -f %s \"$@\"\n"
            (Filename.quote binary)
            (Filename.quote config_file));
      Unix.chmod wrapper 0o700;
      let channel = Unix.open_process_args_in binary [| binary; "-V" |] in
      let version =
        Fun.protect
          ~finally:(fun () ->
            if Unix.close_process_in channel <> Unix.WEXITED 0 then
              failwith "tmux version discovery failed")
          (fun () -> String.trim (input_line channel))
      in
      if not (String.starts_with ~prefix:"tmux " version) then
        failwith "invalid tmux version";
      let version = String.sub version 5 (String.length version - 5) in
      let ready = Filename.concat directory "ready.txt" in
      Out_channel.with_open_text ready (fun output ->
          output_string output "showcase ready\n");
      let spawn =
        Spawn.make (Exec { program = "/bin/cat"; args = [ "-u"; ready; "-" ] })
      in
      let config =
        match
          Config.make ~directory ~session:"showcase"
            [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
      in
      let plan =
        match
          Plan.make
            ~endpoint:(Endpoint.socket_path socket)
            ~version ~observed:None config
        with
        | Ok value -> value
        | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
      in
      let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in
      Fun.protect
        ~finally:(fun () -> get (Workspace.close operation))
        (fun () ->
          let receipt = get (Workspace.await operation) in
          if receipt.status <> Execution.Succeeded then
            failwith "workspace setup failed";
          let client = Option.get (Workspace.client operation) in
          let initial = List.hd (Iarray.get receipt.steps 0).created in
          Printf.printf "socket=%s\n%!" socket;
          let pane =
            Snapshot.find_pane
              (get (Client.capture client))
              (Pane.Target.id initial.pane)
            |> Option.get
          in
          ignore
            (get
               (Client.wait_for_text ~timeout:0.5 client pane
                  ~text:"showcase ready"));
          use client initial spawn))


let () =
  let binary = if Array.length Sys.argv = 2 then Sys.argv.(1) else "tmux" in
  with_demo binary set_layout
```

APIs used

* [`Client.split_pane`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-split_pane/)
* [`Client.select_layout`](https://libtmux.org/en/ocaml/latest/reference/libtmux_unix-client-select_layout/)
* [`Pane.width`](https://libtmux.org/en/ocaml/latest/reference/libtmux-pane-width/)

tmux 3.3+ The whole program, [`examples/showcase_layout.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_layout.ml). The port's CI runs it in `.github/workflows/ocaml.yml`: `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"`.

[Core Library documentation →](https://libtmux.org/en/ocaml/latest/core/)[More OCaml examples →](https://libtmux.org/en/ocaml/latest/examples/)

### Install `libtmux`

```console
$ git clone https://github.com/libtmux/libtmux-ocaml.git && \
  cd libtmux-ocaml && \
  opam install .
```

Source installation; no registry release. Requires OCaml 5.5.2, Linux and tmux 3.3a through 3.8.

## Workspace Manager

### Install `libtmux-workspace-cli`

The workspace package is separate from the core library.

```console
$ git clone https://github.com/libtmux/libtmux-ocaml.git && \
  cd libtmux-ocaml && \
  opam install .
```

Build the CLI from source, then use tmux-workspace apply with a validated JSON workspace.

* [Workspace Manager documentation →](https://libtmux.org/en/ocaml/latest/workspace/)

Build the workspace from a program

examples/showcase_graph.ml

```ocaml
let config =
  match
    Config.make ~directory ~session:"showcase"
      [ Config.window ~name:"editor" [ Config.pane ~spawn () ] ]
  with
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Config.pp_error error)
in
let plan =
  match
    Plan.make
      ~endpoint:(Endpoint.socket_path socket)
      ~version ~observed:None config
  with
  | Ok value -> value
  | Error error -> failwith (Format.asprintf "%a" Plan.pp_error error)
in
let operation = get (Workspace.submit_bootstrap ~binary:wrapper plan) in


let receipt = get (Workspace.await operation) in
if receipt.status <> Execution.Succeeded then
  failwith "workspace setup failed";
let client = Option.get (Workspace.client operation) in
let initial = List.hd (Iarray.get receipt.steps 0).created in
```

Excerpt of [`examples/showcase_graph.ml`](https://github.com/libtmux/libtmux-ocaml/blob/205dfda49742592b479247049d186956e4a4cfe9/examples/showcase_graph.ml), which `opam exec -- python3 test/showcase_test.py "$TMUX_BINARY"` runs in the repository's CI (`.github/workflows/ocaml.yml`).

### [Topics](https://libtmux.org/en/ocaml/latest/topics/)

[Subject-by-subject, with OCaml in every code block.](https://libtmux.org/en/ocaml/latest/topics/)

### [Guides](https://libtmux.org/en/ocaml/latest/guides/)

[Task guides for OCaml.](https://libtmux.org/en/ocaml/latest/guides/)

### [Examples](https://libtmux.org/en/ocaml/latest/examples/)

[Complete programs, sourced from the code each port tests.](https://libtmux.org/en/ocaml/latest/examples/)

### [Concepts](https://libtmux.org/en/ocaml/latest/concepts/)

[The domain model every port shares.](https://libtmux.org/en/ocaml/latest/concepts/)

## Packages and integrations

### [Workspace Manager](https://libtmux.org/en/ocaml/latest/workspace/)

[Validate, preview and apply JSON workspaces with retained execution receipts.](https://libtmux.org/en/ocaml/latest/workspace/)

## API references

### [Official reference](https://libtmux.org/en/ocaml/latest/reference/)

[OCaml API signatures and documentation, with links to related guides and APIs in other languages.](https://libtmux.org/en/ocaml/latest/reference/)

[All OCaml versions and install instructions](https://libtmux.org/en/ocaml/)
