An Emacs client for OpenCode. Start servers, open sessions, chat, stream responses, and manage agents — all from native Emacs buffers.
Note: This project is a work-in-progress. It is ready for daily use but does not yet have full feature parity with the OpenCode TUI. See TODO.org for a rough roadmap.
CleanShot.2026-06-23.at.11.37.12.mov
Requirements:
- Emacs 29+
- The
opencodeCLI on your$PATH(install docs) - The Emacs package
request bunornoderecommended for performant SSE streaming;curlis used as a fallback
Clone the repository and add it to your load-path:
(add-to-list 'load-path "/path/to/emacs-opencode")
;; Install `request' from MELPA first, e.g. M-x package-install RET request RET.
(require 'emacs-opencode)Or install with straight.el:
(use-package request
:straight t)
(use-package emacs-opencode
:straight (:type git :host github :repo "jdormit/emacs-opencode"))Run M-x opencode in a project directory to create a new session. Type a
prompt and press C-c C-c to send it. Use C-c C-a to choose an agent,
C-c C-l to choose a model/provider, and C-c C-o to run an OpenCode slash
command.
For a one-shot prompt without opening a session buffer first, use
M-x opencode-ask.
To return to an existing session, use M-x opencode-open-session. To send a
prompt to an existing session from another buffer, use M-x
opencode-send-to-session or M-x opencode-send-context-to-session.
Session buffers are normal Emacs buffers with an editable input area at the end. They support:
- Streaming responses and tool output.
- Agent, model/provider, and variant selection from the header-line controls.
- Slash commands: type
/command argsor runC-c C-o. - Shell prompts: type
!commandto send shell output through OpenCode’s shell prompt endpoint. - Subagent mentions: type
@agentand complete withC-<tab>. - Collapsible long shell/tool output: press
RET,TAB, or click the collapse indicator. - Subagent navigation: press
RET,TAB, or click on a task/subagent block to open its child session. - Reasoning/thinking blocks are hidden by default and can be toggled per buffer
with
C-c C-twhen the OpenCode server has reasoning parts for the session. - Compact a session on demand with
C-c C-x. Compaction keeps the transcript visible in the buffer and inserts a boundary marker showing where earlier messages were summarized for future model context. - Fork a session with
C-c C-f. When point is on a user message, the new session starts before that message; when point is on an assistant message, it starts before the user message that prompted that response. When point is in the input area, the whole session is forked. - Inline handling for OpenCode permission requests and questions via the minibuffer.
Context commands include the active region when one is selected; otherwise they include the 10 lines around point plus file and line metadata when available.
| Command | Description |
|---|---|
opencode | New session in a project directory |
opencode-ask | New session with a prompt |
opencode-ask-contextual | New session with prompt + buffer context |
opencode-open-session | Open an existing session |
opencode-send-to-session | Send a prompt to an existing session |
opencode-send-context-to-session | Send prompt + context to an existing session |
opencode-mcp-status | Show current OpenCode MCP server status |
opencode-shutdown | Stop server for a directory |
opencode-shutdown-all | Stop all servers |
All commands auto-detect the current project root. Use a prefix argument
(C-u) to choose a directory manually.
| Key | Command |
|---|---|
C-c C-c | Send input |
C-c C-k | Interrupt active prompt |
C-c C-a | Select agent |
C-c C-n | Next agent |
C-c C-p | Previous agent |
C-c C-l | Select model/provider |
C-c C-v | Select model variant |
C-c C-] | Next variant |
C-c C-[ | Previous variant |
C-c C-o | Run a slash command |
C-c C-t | Toggle reasoning/thinking blocks |
C-c C-x | Compact session |
C-c C-f | Fork session |
C-c C-r | Refresh agents |
C-<tab> | Completion-at-point (slash commands) |
Under evil-mode, TAB and S-TAB (or <backtab>) cycle to the next and
previous agent in normal and insert states.
Run a one-shot opencode run prompt asynchronously from elisp, without
opening a session buffer. Useful for scripting and building your own commands
on top of OpenCode.
(opencode-run PROMPT CALLBACK
&key model agent command dangerously-skip-permissions)PROMPT is the message string to send. CALLBACK is invoked when the process
finishes. Its arity is inspected automatically: a callback that accepts two or
more arguments receives (OUTPUT SUCCESS-P), while a single-argument callback
receives just OUTPUT. OUTPUT is the model’s plain-text answer (the
formatted header and ANSI control sequences OpenCode writes to standard error
are discarded, and trailing whitespace is trimmed). SUCCESS-P is non-nil when
the process exited successfully.
The keyword arguments map to the corresponding opencode run CLI flags:
| Keyword | Flag |
|---|---|
:model | --model |
:agent | --agent |
:command | --command |
:dangerously-skip-permissions | --dangerously-skip-permissions |
The command runs in default-directory; bind it to control where OpenCode
runs.
(let ((default-directory "~/my-project/"))
(opencode-run "How many Rs are in strawberry?"
(lambda (output success-p)
(when success-p
(message "%s" output)))
:agent "build"))Options and faces are in the emacs-opencode customize group. Common options:
| Variable | Default | Description |
|---|---|---|
opencode-server-command | "opencode" | Executable name or path |
opencode-server-host | "127.0.0.1" | Server bind address |
opencode-server-port | 4096 | Server port (auto-picks a free port if busy) |
opencode-server-environment | nil | Alist of env vars for server processes |
opencode-ready-timeout | 5 | Seconds to wait for server startup |
opencode-session-list-limit | 1000 | Max sessions requested for completion |
opencode-session-default-agent | "plan" | Default agent for new sessions |
opencode-session-default-variant | nil | Default model variant |
opencode-session-input-prompt | "❯ " | Prompt string in session buffers |
opencode-session-show-reasoning | nil | Show reasoning/thinking blocks by default |
opencode-session-bash-output-max-lines | 10 | Max lines of bash output shown by default |
opencode-session-completion-providers | (list ...) | Completion-at-point providers for input |
opencode-session-spinner-frames | (list ...) | Spinner animation frames |
opencode-session-spinner-interval | 0.1 | Seconds between spinner frames |
opencode-session-header-retry-message-max | 60 | Max length for retry messages in header |
SSE and diagnostics options:
| Variable | Default | Description |
|---|---|---|
opencode-sse-backend | auto | Prefer JS bridge, fall back to curl |
opencode-sse-curl-command | "curl" | Curl executable for fallback streaming |
opencode-sse-log-output | nil | Log raw SSE output to the process buffer |
opencode-sse-log-max-size | 1048576 | Max bytes kept in SSE log buffer |
opencode-sse-profile-enabled | nil | Collect SSE performance timing data |
opencode-sse-profile-ring-size | 1000 | Number of event profile records to keep |
opencode-sse-profile-slow-gap-threshold-ms | 50.0 | Threshold for recording slow chunk gaps |
opencode-sse-profile-slow-gap-ring-size | 200 | Number of slow gap records to keep |
Faces (opencode-session-user-face, opencode-session-assistant-face,
opencode-session-tool-face, opencode-session-compaction-face, etc.) can be
customized via M-x customize-group RET emacs-opencode.
With a small amount of configuration, the OpenCode agent can interact with your live Emacs session: reading buffers, evaluating elisp, running compilation commands, and more. There are a few ways to wire this up, depending on how much access you want the agent to have and how you prefer to manage tools.
Two useful starting points:
- emacs-mcp-server exposes Emacs primitives as MCP tools. This is the most direct way to give OpenCode structured access to buffers, diagnostics, elisp evaluation, and related operations.
- emacsclient skill shows a lighter-weight pattern where the agent uses
emacsclientto evaluate elisp or run commands against an existing Emacs server.
Configure either approach however you prefer, then point OpenCode at an Emacs-specific instruction file so the agent knows those tools are available.
Create a dedicated config file (e.g. ~/.config/opencode/opencode.emacs.jsonc)
that loads instructions telling the agent it’s running inside Emacs:
If you use an MCP server, add its mcp entry to this config using the setup
recommended by that server. If you use emacsclient, make sure Emacs is running
as a server and emacsclient is on the PATH inherited by OpenCode.
And the instructions file:
<!-- ~/.config/opencode/instructions/emacs.md -->
# Emacs Session Instructions
You are running inside Emacs. You have access to the Emacs environment via
the available Emacs integration tools, such as MCP tools or emacsclient. Use
them whenever it would be best for the task at hand, e.g. reading buffers,
evaluating elisp, fetching diagnostics, or running user-facing shell commands
(tests, compilation) via `compile`.Use opencode-server-environment to point the server process at the
Emacs-specific config and pass any secrets that MCP servers or tools need:
(use-package emacs-opencode
:straight (:type git :host github :repo "jdormit/emacs-opencode")
:commands (opencode opencode-open-session opencode-ask opencode-ask-contextual)
:custom
(opencode-server-environment
`(("OPENCODE_CONFIG" . ,(expand-file-name "~/.config/opencode/opencode.emacs.jsonc"))
;; Pass any additional env vars your MCP servers need:
("TAVILY_API_KEY" . "..."))))With this setup, every OpenCode server started from Emacs will load the Emacs-specific config and instructions. The agent will know it’s running inside Emacs and can use whichever Emacs integration tools you configured.