An ACP (Agent Client Protocol) client designed for integration with Neovim.
Hermes is a messaging layer for Neovim. It has no built-in UI, instead it provides APIs and hooks for building your own workflow while routing client-agent communication.
Hermes focuses on:
- APIs for making requests to AI Assistants (prompt, connect, authenticate, etc)
- Hooks into requests from AI assistants that require responses (permission requests, access requests, etc)
- Autocommands for updates on communication between the user (client) and assistant (agent)
- Full implementation of ACP Client (Built on the official Rust ACP Sdk)
- Support for all registered ACP Agents (Full list here)
- Configurable capabilities (filesystem, terminal, etc)
- Autocommands for messages/notifications
- Communication over Stdio, TCP, Socket, and HTTP protocols
- Neovim 0.12 progress integration via
nvim_echowithkind="progress"
vim.pack (v0.12+)
vim.pack.add({ "Ruddickmg/hermes.nvim" })lazy.nvim
{
"Ruddickmg/hermes.nvim",
config = function()
require("hermes").setup()
end
}rocks.nvim
:Rocks install hermes.nvim- Neovim 0.11 or later
Hermes is built in Rust and so must be integrated with lua during installation, pre built binaries are provided for convenience
Binaries are available for:
- Linux: x86_64, aarch64 (arm64)
- macOS: x86_64, arm64
- Windows: x86_64
Note
Hermes will automatically detect and download a pre-built binary for supported platforms.
It will:
- Check if your platform is supported
- Download the appropriate pre-built binary from GitHub releases
- Load the binary
This happens automatically on first API call.
-- sets up pre-built binary for your system
require("hermes").setup({
download = {
version = "latest",
}
})You can also disable this if you would prefer to build from source
-- Will have to set up manually with `:Hermes build` or build manually from source
require("hermes").setup({
download = {
auto = false,
},
})If your platform is not in the supported list above, you can build from source:
Requirements:
- Rust toolchain (1.70 or later)
Scripted
Run the build command in Neovim:
:Hermes buildThis will:
- Compile the Rust code with
cargo build --release - Install the resulting binary in the correct location
Manual
git clone https://github.com/Ruddickmg/hermes.nvim.git
cd hermes.nvim
cargo build --release
# Copy target/release/libhermes.* to your Neovim data directoryNote
Hermes can pre-render icons during the build step to avoid having to do so at runtime.
Using the build script:
:Hermes build with-iconsOr building manually:
cargo build --release --features with-iconsShow recent log messages and current state information.
:Hermes logFetch the latest release from GitHub and replaces the current binary.
:Hermes updateTriggers Progress autocommand while downloading
Download the currently configured version.
:Hermes installTriggers Progress autocommand while downloading
Remove the binary. Run :Hermes install or use Hermes API to re-download.
:Hermes cleanCompile from source (requires Rust toolchain). Runs asynchronously without blocking Neovim.
:Hermes buildCancel an in-progress source build. Shows warning if no build is running.
:Hermes cancelHermes exposes the following functions for sending requests to AI assistants.
Warning
Methods marked βOptionalβ are implemented by Hermes but are not mandatory for agent implementations.
Configure Hermes plugin settings.
local hermes = require("hermes")
-- Basic usage with no arguments (uses all defaults)
hermes.setup()
-- Configure specific permissions
hermes.setup({
permissions = {
fs_write_access = true,
terminal_access = true,
}
})
-- Full configuration defaults
hermes.setup({
download = {
version = "latest", -- specify which hermes release to use
auto = true, -- automatically download pre-built binary (set to false to build manually)
timeout = 60, -- timeout in seconds for download
},
root_markers = { ".git" }, -- used to detect the project root by matching file names in the root directory
permissions = {
fs_write_access = true, -- Allow file writes to the agent
fs_read_access = true, -- Allow file reads to the agent
terminal_access = true, -- Allow terminal access to the agent
request_permissions = true, -- Allow agent to send permission requests
send_notifications = true, -- Allow the agent to send notifications
},
distributions = { -- path to distributions
uvx = true, -- allows installing agents via UVX
npx = true, -- allows installing agents via NPX
binary = { -- configuration for binary behavior
path = vim.fn.stdpath('state') .. "/nvim/hermes/binaries", -- path where binaries will be downloaded to
enabled = true, -- allows installing agents via binary
},
},
terminal = {
delete = true, -- Auto-delete terminals on exit
enabled = true, -- Enable terminal functionality
buffered = true, -- Buffer terminal output
},
buffer = {
auto_save = false, -- Auto-save modified files after writing to them
},
session = {
store_history = true, -- Store session history locally when agent does not support load_session
},
log = {
-- send logs to stdio
stdio = {
-- only logs of the set value and above will be sent
level = vim.log.levels.OFF or "off",
-- logs stdio logs will be formatted with the selected format
format = "compact",
-- show ansi symbols
show_ansi = false,
},
-- send logs to Neovim "notify"
notification = {
level = vim.log.levels.ERROR or "error",
show_ansi = false, -- show ansi symbols
format = "compact",
},
-- send logs to Neovim ":messages"
message = {
level = vim.log.levels.OFF or "off",
show_ansi = false, -- show ansi symbols
format = "compact",
},
-- send logs to log files
file = {
level = vim.log.levels.OFF or "off",
format = "json",
path = vim.fn.stdpath('state') .. "/nvim/hermes/logs", -- path to log file(s)
name = "hermes.log", -- name of log file
show_ansi = false, -- show ansi symbols
max_size = 10485760, -- 10mb in bytes
max_files = 5, -- Max log files to generate
},
},
progress = {
cmdline = false, -- Show progress messages in cmdline (0.12+ only)
update_frequency = 150 -- how often to poll the download for progress in milliseconds
},
})Note
setup()does not have to be called, hermes will operate off of defaults without it- Configuration changes are applied immediately
- Multiple
setup()calls merge configurations - only specified fields are updated - All unspecified fields preserve their existing values
This method retrieves a list of available agents to choose from.
local hermes = require("hermes")
-- get list of agents
hermes.agents()
-- example with config options
hermes.agents({
update = true, -- pull down the most up to date list from the ACP registry (will use last downloaded/pre-bundled registry if false)
url = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json", -- URL to pull ACP registry from
})Triggers:
This method allows you to connect to an agent, it takes the agent name and the protocol for the connection (defaults to stdio).
local hermes = require("hermes")
-- connect to pre-defined agent
hermes.connect("github-copilot-cli")
-- configure protocol
hermes.connect("opencode", {
protocol = "http",
})
-- configure distribution
hermes.connect("kilo", {
-- Some agents have multiple different distributions; you can specify which to use.
distribution = "binary", -- "binary", "npx", and/or "uvx"
})
-- connect to custom agent (not pre-defined)
hermes.connect(
"my-claude", -- this will be the key you use for other methods (disconnect for example)
{
protocol = "socket", -- optional (Defaults to "stdio")
command = "claude-acp",
args = { "--socket", "/tmp/claude.sock" },
}
)
-- connect to TCP socket
hermes.connect(
"copilot",
{
protocol = "tcp",
host = "localhost",
port = 8080,
}
)
--example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "AgentList",
callback = function(args)
local agent = table.remove(args.data.agents) -- select auth method id somehow
hermes.connect(agent.id, {
distribution = table.remove(agent.distributions), -- select distribution somehow
})
end,
})Triggers:
- ConnectionInitialized autocommand upon completion.
- Progress autocommand if the
distributions.binary.enabledconfiguration is enabled (reports binary download progress)
Below are examples of how you can disconnect from agent(s).
local hermes = require("hermes")
-- disconnect from a single agent
hermes.disconnect("copilot");
-- disconnect from a list of agents
hermes.disconnect({ "copilot", "opencode" })
-- disconnect from all agents
hermes.disconnect()Below are examples of how you can log out from agent(s).
local hermes = require("hermes")
-- logout a single agent
hermes.logout("copilot");
-- logout a list of agents
hermes.logout({ "copilot", "opencode" })
-- logout all agents
hermes.logout()Triggers: LoggedOut autocommand upon completion.
Handle agent authentication.
local hermes = require("hermes")
-- function signature
hermes.authenticate(auth_method_id)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ConnectionInitialized",
callback = function(args)
local auth_method_id = table.remove(args.data.authMethods).id -- select auth method id somehow
hermes.authenticate(auth_method_id)
end,
})Triggers: Authenticated autocommand upon completion.
Note on
promptId: All agent notification autocommands (sourced from π€ Agent) include apromptIdfield. This UUID is generated when a prompt is sent (viaprompt()) or when a user message chunk arrives from the agent, and it is attached to all subsequent notifications for that session so you can correlate messages within a single prompt/response cycle.
Send prompts to the agent
There are five types of prompts you can send to an agent
- text: Human readable prompts
- link: Links to resources (url, file path, etc)
- embedded: Similar to a link, but including the contents of the resource link (preferred over link if available)
- image: An image (encoded as a base64)
- audio: Audio content for communication (encoded as base64)
local hermes = require("hermes")
local session_id = "current-session-id";
-- single prompt call signature
hermes.prompt(sessionId, {
type = "text",
text = "What time is it?"
})
-- multiple prompt call signature
hermes.prompt(session_id, {
{
type = "text",
text = "What time is it?"
},
{
type = "link",
name = "Example file",
uri = "/path/to/example.txt"
},
{ -- text
type = "embedded",
resource = {
uri = "file:///home/user/script.py",
mimeType = "text/x-python",
text = "def hello():\n print('Hello, world!')"
}
},
{ -- blob
type = "embedded",
resource = {
uri = "file:///home/user/script.py",
mimeType = "application/pdf",
blob = "Base64-encoded binary data"
}
},
{
type = "image",
data = "base64-encoded-image-data",
mimeType = "image/png"
},
{
type = "audio",
data = "base64-encoded-audio-data",
mimeType = "audio/wav"
}
}
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionCreated",
callback = function(args)
local sessionId = args.data.sessionId
local prompt_id = hermes.prompt(sessionId, {
type = "text",
text = "What time is it?"
})
end,
})Triggers: Prompted autocommand upon completion.
Create a new session. If no arguments are provided, the session defaults to either the project root or the current directory.
local hermes = require("hermes")
-- use default session configuration
hermes.create_session()
-- customize connection configuration
hermes.create_session({
cwd = ".", -- path to create the session in (optional)
additional_directories = {}, -- more directories to include in addition to the root directory (relative paths are relative to cwd)
mcp_servers = {
{ -- Http or Sse MCP server definition
type = "http", -- or "sse"
name = "Human readable name for MCP server",
url = "http://url-to-mcp-server.com",
headers = {
{ ["Content-Type"] = "application/json" },
{ header_name = "header value" },
},
},
{ -- Stdio MCP server definition
type = "stdio",
name = "Human readable name for MCP server",
command = "/path/to/the/MCP/server/executable",
args = { "run", "--flag", "something" },
-- Environment variables to set when launching the MCP server.
env = {
{ name = "ENVIRONMENT_VAR_NAME", value = "value" },
},
},
},
})Triggers: SessionCreated autocommand upon completion.
Load an existing session (replays session history)
local hermes = require("hermes")
local session_id = "some-session-id"
-- call signature (uses defaults)
hermes.load_session(session_id)
-- call signature (with further configuration)
hermes.load_session(session_id, {
cwd = ".", -- path to load the session from (optional, defaults to either project root or current directory)
additional_directories = {}, -- more directories to include in addition to the root directory (relative paths are relative to cwd)
mcp_servers = {
{ -- Http or Sse MCP server definition
type = "http", -- or "sse"
name = "Human readable name for MCP server",
url = "http://url-to-mcp-server.com",
headers = {
{ ["Content-Type"] = "application/json" },
{ header_name = "header value" },
},
},
{ -- Stdio MCP server definition
type = "stdio",
name = "Human readable name for MCP server",
command = "/path/to/the/MCP/server/executable",
args = { "run", "--flag", "something" },
-- Environment variables to set when launching the MCP server.
env = {
{ name = "ENVIRONMENT_VAR_NAME", value = "value" },
},
},
},
})
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionCreated",
callback = function(args)
local session_id = args.data.sessionId
hermes.load_session(session_id)
end,
})Triggers: SessionLoaded autocommand upon completion
Resume an existing session (without replaying the session history)
local hermes = require("hermes")
local session_id = "some-session-id"
-- call signature (uses defaults)
hermes.resume_session(session_id)
-- call signature (with further configuration)
hermes.resume_session(session_id, {
cwd = ".", -- path to load the session from (optional, defaults to either project root or current directory)
additional_directories = {}, -- more directories to include in addition to the root directory (relative paths are relative to cwd)
mcp_Servers = {
{ -- Http or Sse MCP server definition
type = "http", -- or "sse"
name = "Human readable name for MCP server",
url = "http://url-to-mcp-server.com",
headers = {
{ ["Content-Type"] = "application/json" },
{ header_name = "header value" },
},
},
{ -- Stdio MCP server definition
type = "stdio",
name = "Human readable name for MCP server",
command = "/path/to/the/MCP/server/executable",
args = { "run", "--flag", "something" },
-- Environment variables to set when launching the MCP server.
env = {
{ name = "ENVIRONMENT_VAR_NAME", value = "value" },
},
},
},
})
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionCreated",
callback = function(args)
local seesion_id = args.data.sessionId
hermes.resume_session(session_id)
end,
})Triggers: SessionResumed autocommand upon completion
List sessions, can be filtered by project path or cursor pagination.
local hermes = require("hermes")
-- list all sessions
hermes.list_sessions()
-- filter by directory
hermes.list_sessions({
cwd = "/path/to/directory",
})
-- filter by cursor based pagination
hermes.list_sessions({
cursor = "abc123",
})
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionsListed",
callback = function(args)
local next_page = args.data.nextCursor
-- get next page of sessions with this cursor in the current directory
hermes.list_sessions({
cwd = vim.fn.getcwd(),
cursor = next_page,
})
end,
})Triggers: SessionsListed autocommand upon completion
Close active session and free all resources associated with it
local hermes = require("hermes")
local session_id = "session-id-from-create-session-response"
-- call signature
hermes.close_session(session_id)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionCreated",
callback = function(args)
local session_id = args.data.sessionId
hermes.close_session(session_id)
end,
})Triggers: SessionClosed autocommand upon completion
Delete session from the session list
local hermes = require("hermes")
local session_id = "session-id-from-create-session-response"
-- call signature
hermes.delete_session(session_id)
-- delete multiple sessions
hermes.delete_session({ session_id, "other-session-id" })
-- cancel session(s) before deleting them (defaults to true; set to false to disable this behavior)
hermes.delete_session(session_id, { cancel = true })
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionCreated",
callback = function(args)
local session_id = args.data.sessionId
hermes.delete_session(session_id)
end,
})Triggers: SessionDeleted autocommand upon completion [!WARNING]
Cancel the current operation of the agent (e.g., stop generating text, stop a tool call in progress, etc)
local hermes = require("hermes")
local sessionId = 'session-id-from-create-session-response'
-- call signature
hermes.cancel(sessionId)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionCreated",
callback = function(args)
local sessionId = args.data.sessionId
hermes.cancel(sessionId)
end,
})Set what mode the agent is in (the plan/build modes for opencode for example)
local hermes = require("hermes")
local session_id = "some-session-id"
local mode_id = "some-mode-id"
-- call signature
hermes.set_mode(session_id, mode_id)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ModeUpdated",
callback = function(args)
print("Mode changed to: " .. args.data.name)
end,
})Triggers: ModeUpdated autocommand upon completion.
Get the selectable modes for a session.
Fires a Modes User autocommand with the selection data instead of returning it.
local hermes = require("hermes")
-- call signature
hermes.modes(session_id)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "Modes",
callback = function(args)
local options = args.data.options
for _, option in ipairs(options) do
print("Mode: " .. option.name .. " (value: " .. option.value .. ")")
end
end,
})Triggers: Modes autocommand with a
Selectionpayload containing:
options(array): Available mode optionscurrent(object): The currently selected mode
Set what model the agent is using.
local hermes = require("hermes")
-- call signature
hermes.set_model(sessionId, modelId)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "SessionModelUpdated",
callback = function(args)
print("Model changed to: " .. args.data.name)
end,
})Triggers: SessionModelUpdated autocommand upon completion.
Get the selectable models for a session.
Fires a Models User autocommand with the selection data instead of returning it.
local hermes = require("hermes")
local session_id = "some-session-id"
-- call signature
hermes.models(session_id)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "Models",
callback = function(args)
local options = args.data.options
for _, option in ipairs(options) do
print("Model: " .. option.name .. " (value: " .. option.value .. ")")
end
end,
})Triggers: Models autocommand with a
Selectionpayload containing:
options(array): Available model optionscurrent(object): The currently selected model
Get the model configuration options for a session.
Fires a ModelConfigurations User autocommand with the configuration data instead of returning it.
local hermes = require("hermes")
-- call signature
hermes.model_configurations(session_id)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ModelConfigurations",
callback = function(args)
local configs = args.data
for _, config in ipairs(configs) do
print("Config: " .. config.name .. " (current: " .. config.selection.current.value .. ")")
end
end,
})Triggers: ModelConfigurations autocommand with an array of model configuration options.
Configure a model option for a session. Takes a table with id and value keys.
local hermes = require("hermes")
-- call signature
hermes.configure_model(session_id, { id = "context_size", value = "200k" })
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ModelConfigurationUpdated",
callback = function(args)
print("Model configuration updated")
end,
})Triggers: ModelConfigurationUpdated autocommand upon completion.
Set the thought level for a session.
local hermes = require("hermes")
-- call signature
hermes.set_thought_level(sessionId, thoughtLevelId)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ThoughtLevelUpdated",
callback = function(args)
print("Thought level changed to: " .. args.data.name)
end,
})Triggers: ThoughtLevelUpdated autocommand upon completion.
Get the selectable thought levels for a session.
Fires a ThoughtLevels User autocommand with the selection data instead of returning it.
local hermes = require("hermes")
-- call signature
hermes.thought_levels(sessionId)
-- example
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ThoughtLevels",
callback = function(args)
local options = args.data.options
for _, option in ipairs(options) do
print("Thought Level: " .. option.name .. " (value: " .. option.value .. ")")
end
end,
})Triggers: ThoughtLevels autocommand with a
Selectionpayload containing:
options(array): Available thought level optionscurrent(object): The currently selected thought level
When an agent makes a request that requires user input (such as a permission request), it triggers an autocommand and pauses until the user responds. Use the respond method with the request ID to resume the agent's operation. If no autocommand handler is defined, a default workflow will be triggered. Requests can be disabled via the setup configuration.
Warning
While Hermes is a complete ACP client, most agents available today don't fully utilize the protocol. The following autocommands are optional features and often handled through agent-specific tools rather than calling the ACP methods that trigger them. This means some Hermes capabilities may not be exercised depending on which agent you use.
local hermes = require("hermes")
-- call signature
hermes.respond("requestId", "optionId")
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "PermissionRequest",
callback = function(args)
local selected_option_id = table.remove(args.data.options).optionId -- select id somehow
local request_id = args.data.requestId
hermes.respond(request_id, selected_option_id)
end,
})Responds to: PermissionRequest autocommand.
Default behavior: If no autocommand handler is defined for
PermissionRequest, Hermes will use the native Neovim select menu to gather a response from the user.
local hermes = require("hermes")
-- call signature
hermes.respond("requestId")
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "WriteTextFile",
callback = function(args)
local request_id = args.data.requestId
-- writing to a file doesn't take any data, but a notification is required when it is finished
hermes.respond(request_id)
end,
})Responds to: WriteTextFile autocommand.
Default behavior: If no autocommand handler is defined for
WriteTextFile, Hermes will:
- Update buffers if they are open and mark them as modified (will not automatically save)
- Refresh the view of any modified buffers
- Write to the file on disk if it is not open in a buffer
local hermes = require("hermes")
-- call signature
hermes.respond("requestId", "Hello World!")
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "ReadTextFile",
callback = function(args)
local requestId = args.data.requestId
local filename = args.data.path
local _start = args.data.line -- optional, may not be provided by the agent
local _end = args.data.limit -- optional, may not be provided by the agent
local file = io.open(filename, "r")
local content = file:read("*all")
file:close()
hermes.respond(requestId, content)
end,
})Responds to: ReadTextFile autocommand.
Default behavior: If no autocommand handler is defined for
ReadTextFile, Hermes will:
- Read the file from disk if one is not open in a buffer
- Read the current state of the open buffer if the target file is open
- Start at the
linenumber if defined- End at the
limitnumber if defined
local hermes = require("hermes")
local terminals = {}
-- call signature
hermes.respond("requestId", "terminalId")
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "TerminalCreate",
callback = function(event)
local terminal_id = "your-generated-terminal-id" -- generate a unique id for terminal
local request_id = event.data.requestId
local command = event.data.command
local term_args = event.data.args or {}
local byte_limit = event.data.output_byte_limit
-- lua combines args and command (add command to the beginning of args)
table.insert(term_args, 1, command)
terminals[terminal_id] = vim.fn.jobstart(term_args, {
env = event.data.env,
cwd = event.data.cwd,
})
hermes.respond(request_id, terminal_id);
end,
})Responds to: TerminalCreate autocommand.
Default behavior: If no autocommand handler is defined for
TerminalCreate, Hermes will:
- Create a terminal attached to a buffer (hidden by default)
- Handle byte limit constraints if defined
Warning
If no TerminalCreate autocommand is registered, Hermes will use default functionality to manage all subsequent terminal interaction.
local hermes = require("hermes")
local terminals = {}
local is_truncated = true;
-- call signature (truncated defaults to false)
hermes.respond("requestId", "terminal output text")
-- call signature with truncation defined
hermes.respond("requestId", {
output = "terminal output text",
truncated = is_truncated,
})
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "TerminalOutput",
callback = function(args)
local requestId = args.data.requestId
local terminalId = args.data.terminalId
local terminalOutput = terminals[terminalId].output -- get output somehow
hermes.respond(requestId, terminalOutput);
end,
})Responds to: TerminalOutput autocommand.
Default behavior: If no autocommand handler is defined for TerminalCreate, Hermes will:
- Collect and send the terminal output to the agent
local hermes = require("hermes")
local terminals = {}
-- call signature for termination signal
hermes.respond("requestId", "SIGTERM")
-- call signature for exit code
hermes.respond("requestId", 0)
-- call signature with exit code and termination signal
hermes.respond("requestId", {
exitCode = 9,
signal = "SIGKILL"
})
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "TerminalExit",
callback = function(args)
local requestId = args.data.requestId
local terminalId = args.data.terminalId
hermes.respond(requestId, {
exitCode = terminals[terminalId].exitCode, -- get output somehow
signal = terminals[terminalId].signal, -- get output somehow
});
end,
})Responds to: TerminalExit autocommand.
Default behavior: If no autocommand handler is defined for TerminalCreate, Hermes will:
- Wait for and report terminal exit details
local hermes = require("hermes")
local terminals = {}
-- call signature with exit code and termination signal
hermes.respond("requestId")
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "TerminalKill",
callback = function(args)
local request_id = args.data.requestId
hermes.respond(request_id);
end,
})Responds to: TerminalKill autocommand.
Default behavior: If no autocommand handler is defined for TerminalCreate, Hermes will:
- Stop the process running in the terminal
local hermes = require("hermes")
local terminals = {}
-- call signature with exit code and termination signal
hermes.respond("requestId")
-- example:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "TerminalRelease",
callback = function(args)
local request_id = args.data.requestId
hermes.respond(request_id);
end,
})Responds to: TerminalRelease autocommand.
Default behavior: If no autocommand handler is defined for TerminalCreate, Hermes will:
- Stop any process running in the terminal
- Remove the terminal
- Delete the attached buffer (can be configured to omit this step)
Hermes generates autocommands for all communication between agent and client. Here's an example of hooking into one:
vim.api.nvim_create_autocmd("User", {
group = "hermes",
pattern = "AgentTextMessage",
callback = function(args)
print("Received some text from our assistant: " .. args.data.update.content.text)
end,
})Below is a list of all autocommands and their associated data (passed to the callback in the args.data field). Hermes will only trigger autocommands if there is a listener defined for it (I.E. You have created one like the example above)
| Autocommand | Description | Source | Schema |
|---|---|---|---|
AgentImageMessage |
An image from the agent | π€ Agent | |
AgentImageThought |
Visual reasoning/thought from the agent | π€ Agent | |
AgentList |
List of available agents | β‘ agents() | |
AgentResourceLinkMessage |
A resource link from the agent | π€ Agent | |
AgentResourceLinkThought |
Resource link thought from the agent | π€ Agent | |
AgentResourceMessage |
A resource from the agent | π€ Agent | |
AgentResourceThought |
Resource-based thought from the agent | π€ Agent | |
AgentTextMessage |
A text message from the agent | π€ Agent | |
AgentTextThought |
Textual thought/reasoning from the agent | π€ Agent | |
Authenticated |
Authentication completed | β‘ authenticate() | |
AvailableCommands |
Available commands are updated | π€ Agent | |
ConfigurationOption |
Configuration option updates | π€ Agent | |
ConfigurationUpdated |
Session configuration updated | β‘ set_session_config_option() | |
ConnectionInitialized |
Connection established with agent | β‘ connect() | |
LoggedOut |
Agent logout completed | β‘ logout() | |
ModeCurrent |
Current mode changes | π€ Agent | |
ModelConfigurations |
Available model configuration options retrieved | β‘ model_configurations() | |
ModelConfigurationUpdated |
Model configuration options updated | β‘ configure_model() | |
Models |
Available models retrieved | β‘ models() | |
Modes |
Available modes retrieved | β‘ modes() | |
ModeUpdated |
Session mode changed | β‘ set_mode() | |
PermissionRequest |
Agent requests permission to execute a tool | π€ Agent (requires -> respond()) | |
Plan |
Agent generates a plan | π€ Agent | |
Progress |
Download progress update (binary, registry, agent) | β‘ :Hermes install, :Hermes update, agents(), connect(), setup() | |
Prompted |
Agent response received | β‘ prompt() | |
ReadTextFile |
Agent requests to read a text file | π€ Agent (requires -> respond()) | |
SessionClosed |
Active session closed | β‘ close_session() | |
SessionCreated |
New session created | β‘ create_session() | |
SessionDeleted |
Session deleted | β‘ delete_session() | |
SessionForked |
Session forked successfully | β‘ fork_session() | |
SessionLoaded |
Session loaded successfully | β‘ load_session() | |
SessionModelUpdated |
Session model updated | β‘ set_model() | |
SessionResumed |
Session resumed successfully | β‘ resume_session() | |
SessionsListed |
Session list received | β‘ list_sessions() | |
SessionUpdate |
Session metadata updated (title, timestamps, etc.) | π€ Agent | |
TerminalCreate |
Agent requests to create a terminal for command execution | π€ Agent (requires -> respond()) | |
TerminalExit |
Agent requests notification when terminal process exits | π€ Agent (requires -> respond()) | |
TerminalKill |
Agent requests to kill a terminal process | π€ Agent (requires -> respond()) | |
TerminalOutput |
Agent requests terminal output | π€ Agent (requires -> respond()) | |
TerminalRelease |
Agent requests to release a terminal | π€ Agent (requires -> respond()) | |
ThoughtLevels |
Available thought levels retrieved | β‘ thought_levels() | |
ThoughtLevelUpdated |
Session thought level updated | β‘ set_thought_level() | |
ToolCall |
Agent makes a tool call | π€ Agent | |
ToolCallUpdate |
Tool call is updated (e.g., progress, output) | π€ Agent | |
UsageUpdate |
Session usage metrics update (tokens, cost) | π€ Agent | |
UserImageMessage |
An image sent from the client | π€ Agent | |
UserResourceLinkMessage |
A resource link from the client | π€ Agent | |
UserResourceMessage |
A resource sent from the client | π€ Agent | |
UserTextMessage |
Message text sent from the client | π€ Agent | |
WriteTextFile |
Agent requests to write to a text file | π€ Agent (requires -> respond()) | |
Hermes defaults to INFO log level until configured via setup().
Configure log levels and formats via the setup() function:
require("hermes").setup({
log = {
notification = { level = vim.log.levels.ERROR }, -- Per-target config
message = {
level = vim.log.levels.INFO,
format = "pretty" -- Each target has its own format
},
}
})Log formats can be configured per-target via setup(). Each target has its own format setting that defaults to "compact" if not specified:
require("hermes").setup({
log = {
-- Each target has its own format (defaults to "compact" if not set):
notification = { format = "pretty" },
message = { format = "json" },
file = { format = nil }, -- nil = use default ("compact")
}
})Available formats:
- pretty - Human-readable with colors and formatting
- compact - Condensed single-line format (default)
- full - Complete information including timestamps and metadata
- json - Machine-readable JSON format
Run the health check to see full diagnostics including Neovim version, binary status, platform info, download tools, log files, registry binaries, and configuration.
:checkhealth hermes
-- functionality
- Support "unstable"/proposed ACP methods
-- nice to haves
- research RLM (example)
- connect agent to lsp (try to set it up as a tool call/connect to neovim lsp)
- use whisper.rs to facilitate speech to text