diff --git a/README.md b/README.md
index 677a274..8250f14 100644
--- a/README.md
+++ b/README.md
@@ -161,6 +161,25 @@ listen_on unix:/tmp/kitty
+
+wezterm
+
+```lua
+vim.g.opencode_opts = {
+ provider = {
+ enabled = "wezterm",
+ -- these are defaults set by wezterm
+ wezterm = {
+ direction = "bottom", -- left/right/top/bottom
+ top_level = false,
+ percent = 50,
+ }
+ }
+}
+```
+
+
+
tmux
diff --git a/lua/opencode/provider/init.lua b/lua/opencode/provider/init.lua
index c087625..806274f 100644
--- a/lua/opencode/provider/init.lua
+++ b/lua/opencode/provider/init.lua
@@ -40,12 +40,14 @@
---Default order:
--- - `"snacks"` if `snacks.terminal` is available and enabled
--- - `"kitty"` if in a `kitty` session with remote control enabled
+--- - `"wezterm"` if in a `wezterm` window
--- - `"tmux"` if in a `tmux` session
--- - `false`
----@field enabled? "snacks"|"kitty"|"tmux"|false
+---@field enabled? "snacks"|"kitty"|"wezterm"|"tmux"|false
---
---@field snacks? opencode.provider.snacks.Opts
---@field kitty? opencode.provider.kitty.Opts
+---@field wezterm? opencode.provider.wezterm.Opts
---@field tmux? opencode.provider.tmux.Opts
local M = {}
@@ -71,6 +73,7 @@ function M.list()
return {
require("opencode.provider.snacks"),
require("opencode.provider.kitty"),
+ require("opencode.provider.wezterm"),
require("opencode.provider.tmux"),
}
end
diff --git a/lua/opencode/provider/wezterm.lua b/lua/opencode/provider/wezterm.lua
new file mode 100644
index 0000000..50e87cd
--- /dev/null
+++ b/lua/opencode/provider/wezterm.lua
@@ -0,0 +1,138 @@
+---Provide `opencode` in a [`wezterm`](https://wezterm.org/index.html) pane in the current window.
+---@class opencode.provider.Wezterm : opencode.Provider
+---
+---@field opts opencode.provider.wezterm.Opts
+---@field pane_id? string The `wezterm` pane ID where `opencode` is running (internal use only).
+local Wezterm = {}
+Wezterm.__index = Wezterm
+Wezterm.name = "wezterm"
+
+---`wezterm` options for creating the pane.
+---Strictly mimics the options available in the `wezterm cli split-pane --help` command
+---@class opencode.provider.wezterm.Opts
+---
+---Direction in which the pane that runs opencode is spawn, defaults to bottom
+---@field direction? "left" | "top" | "right" | "bottom"
+---
+---The number of cells that the new split should have expressed as a percentage of the available
+---space, default is 50%
+---@field percent? number
+---
+---Rather than splitting the active pane, split the entire window
+---@field top_level? boolean
+---
+---@param opts? opencode.provider.wezterm.Opts
+---@return opencode.provider.Wezterm
+function Wezterm.new(opts)
+ local self = setmetatable({}, Wezterm)
+ self.opts = opts or {}
+ self.pane_id = nil
+ return self
+end
+
+local function focus_pane(pane_id)
+ vim.fn.system(string.format("wezterm cli activate-pane --pane-id %d", pane_id))
+end
+
+---Check if `wezterm` is running in current terminal.
+function Wezterm.health()
+ if vim.fn.executable("wezterm") ~= 1 then
+ return "`wezterm` executable not found in `$PATH`.",
+ {
+ "Install `wezterm` and ensure it's in your `$PATH`.",
+ }
+ end
+
+ if not vim.env.WEZTERM_PANE then
+ return "Not running inside a `wezterm` window.", {
+ "Launch Neovim inside a `wezterm` window.",
+ }
+ end
+
+ return true
+end
+
+---Get the `wezterm` pane ID where `opencode` is running.
+---@return string|nil pane_id
+function Wezterm:get_pane_id()
+ local ok = self.health()
+ if ok ~= true then
+ error(ok)
+ end
+
+ local base_cmd = self.cmd:match("^%S+") or self.cmd
+ local result = vim.fn.system(
+ string.format("wezterm cli list --format json 2>&1 | jq -r '.[] | select(.title == \"%s\") | .pane_id'", base_cmd)
+ )
+ if result and result ~= "" and not result:match("error") then
+ self.pane_id = result:match("^%d+")
+ else
+ self.pane_id = nil
+ end
+
+ return self.pane_id
+end
+
+---Create or kill the `opencode` pane.
+function Wezterm:toggle()
+ local pane_id = self:get_pane_id()
+ if pane_id then
+ self:stop()
+ else
+ self:start()
+ end
+end
+
+---Start `opencode` in pane.
+function Wezterm:start()
+ local pane_id = self:get_pane_id()
+ if not pane_id then
+ local cmd_parts = { "wezterm", "cli", "split-pane" }
+
+ if self.opts.direction then
+ table.insert(cmd_parts, "--" .. self.opts.direction)
+ end
+
+ if self.opts.percent then
+ table.insert(cmd_parts, "--percent")
+ table.insert(cmd_parts, tostring(self.opts.percent))
+ end
+
+ if self.opts.top_level then
+ table.insert(cmd_parts, "--top-level")
+ end
+
+ table.insert(cmd_parts, "--")
+ table.insert(cmd_parts, self.cmd)
+
+ local result = vim.fn.system(table.concat(cmd_parts, " "))
+ focus_pane(vim.env.WEZTERM_PANE)
+
+ self.pane_id = result:match("^%d+")
+ end
+end
+
+---Kill the `opencode` pane.
+function Wezterm:stop()
+ local pane_id = self:get_pane_id()
+ if pane_id then
+ vim.fn.system(string.format("wezterm cli kill-pane --pane-id %d", pane_id))
+ self.pane_id = nil
+ end
+end
+
+---Focus the `opencode` pane
+--- INFO: This, at the moment, introduces unexpected focusing behaviors with sending `command`s
+--- therefore its disabled.
+function Wezterm:show() end
+-- function Wezterm:show()
+-- local pane_id = self:get_pane_id()
+-- if not pane_id then
+-- vim.notify("No opencode instance is currently running", vim.log.levels.WARN, { title = "opencode" })
+-- return
+-- end
+--
+-- focus_pane(pane_id)
+-- end
+
+return Wezterm