A project-aware LLM editing toolset for Emacs, built on gptel.
macher.mp4
macher allows your LLM to read/search files in the current project, and propose edits in the form of patches, potentially involving multiple files. You can apply the patches directly to your project, or send them back for revision.
macher is a lightweight Emacs-native take on the editing workflows of more full-featured tools like Aider or Plandex. It works with any gptel backend that supports tool use.
It's great for "pseudo-agentic" workflows, for example when you want your LLM to implement a set of changes, maybe call other tools to do research, etc - but you want to review the aggregate changes before writing them to disk.
macher takes inspiration from gptel's flexibility and visibility. It doesn't touch your gptel globals (unless you tell it to) - it's just a set of presets, tools, and transforms that you can use as you like. Or you can use the built-in action commands for a quick and easy workflow.
Example configuration with elpaca + use-package integration:
(use-package macher
:ensure (:host github :repo "kmontag/macher")
:custom
;; The org UI has structured navigation and nice content folding.
(macher-action-buffer-ui 'org)
:config
;; Recommended - register macher tools and presets with gptel.
(macher-install)
;; Recommended - enable macher infrastructure for tools/prompts in
;; any buffer. (Actions and presets will still work without this.)
(macher-enable)
;; Adjust buffer positioning to taste.
;; (add-to-list
;; 'display-buffer-alist
;; '("\\*macher:.*\\*"
;; (display-buffer-in-side-window)
;; (side . bottom)))
;; (add-to-list
;; 'display-buffer-alist
;; '("\\*macher-patch:.*\\*"
;; (display-buffer-in-side-window)
;; (side . right)))
)
(use-package gptel
:config
;; Optional - set up macher as soon as gptel is loaded.
(require 'macher))macher-install and macher-enable are intended to be very unintrusive, and shouldn't have any
effect on non-macher requests, but they do modify the global gptel environment. See the docstrings
if you want more specifics. Action commands will work regardless of whether you've run these.
After calling (macher-install), you can use macher presets in any gptel request or buffer.
macher-gptel-presets.mp4
-
@macher: Full editing capabilities. Adds tools to read, search, and edit files. Changes are captured in memory and displayed as a patch. Also inherits@macher-system. -
@macher-ro: Read-only access. Adds tools to read and search files, but no editing tools. Also inherits@macher-system. -
@macher-tools: Tools only. Enables all macher tools without modifying the system prompt. -
@macher-system: Ensure system prompt includes workspace context (project name and files list). Useful when you want the LLM to understand your project structure but don't need file access. Seemacher-context-string-placeholderandmacher-context-string-function. -
@macher-system-commit: Commits the current workspace context into the system prompt, replacing the dynamic placeholder with a static snapshot. Useful to avoid cache churn in long conversations. -
@macher-base: Utility preset that enables macher tool infrastructure. This gets applied automatically when using other macher presets, but you may want to apply it globally viamacher-enable, if you want to use macher tools and/or dynamic context in more generic contexts. This adds transforms that run on every outgoing request, which:- provide macher tools with a shared editing environment
- replace instances of the
macher-context-string-placeholderin the system prompt
This preset shouldn't have any effect on requests that don't use macher tools or include the placeholder.
All built-in presets can safely be repeatedly applied.
Actions are convenience commands that use macher presets with a specific workflow:
- A prompt is captured based on the current selection/cursor position.
- The prompt is sent in a dedicated actions buffer, with a macher preset applied.
The built-in actions are:
macher-implement: Request an implementation based on selected text or manual input. Uses@macher.macher-revise: Request revisions to the current patch. The patch content and original prompt are included automatically. Uses@macher.macher-discuss: Ask questions about the workspace. Uses@macher-ro.
Typical workflow:
- Select text describing what you want to implement
- Run
M-x macher-implement - Review the patch in the diff buffer
- Apply changes with
C-c C-a(apply hunk) orM-x diff-apply-buffer - Use
M-x macher-reviseif you need changes
Other action-related commands:
macher-action: Run any action from themacher-actions-alist.macher-abort: Cancel running action requests for the current workspace.
The actions buffer UI can be customized with macher-action-buffer-ui (see
Customization).
You can define custom actions in macher-actions-alist.
macher can add information about the current workspace (project name, location, file listings) to your system prompt.
If you've called (macher-enable) and/or activated @macher-base, any instances of the
macher-context-string-placeholder (default: "[macher_placeholder]") in your system prompt will
be replaced with a description of the request buffer's workspace.
The @macher, @macher-ro, and @macher-system presets will append the placeholder automatically
to your system prompt (unless it's already there).
You can also use the macher-context-string-placeholder in your own directives to control placement
or add info about the current project to any gptel request.
The workspace description can be customized by setting the macher-context-string-function.
The workspace is the set of files macher can read and edit. By default, macher supports:
project: A project.el projectfile: A single non-project file
The workspace is determined by macher-workspace-functions. All file operations are restricted to
the current workspace.
To add custom workspace types, extend macher-workspace-types-alist and
macher-workspace-functions.
When you send a macher request with tools:
- When a tool is first invoked, a
macher-contextstruct is created - an ephemeral file-editing environment. - The LLM uses tools to read/search/edit files in this environment.
- All changes are captured in memory (never written to disk).
- When the request completes,
macher-process-request-functiongenerates a unified diff. - The patch is displayed in the workspace's patch buffer.
The macher-context is created lazily (only when tools are actually used) and maintains two
versions of each accessed file:
- Original content: Read-only snapshot from first access
- Modified content: Editable copy where the LLM makes changes
Tools are defined in macher-tools and structured similarly to the MCP filesystem
server.
Available tools:
read_file_in_workspace: Read file contentssearch_in_workspace: Regex search (grep-like)list_directory_in_workspace: List directory contentsedit_file_in_workspace: Make exact string replacementsmulti_edit_file_in_workspace: Multiple edits to one filewrite_file_in_workspace: Create or overwrite filesmove_file_in_workspace: Move/rename filesdelete_file_in_workspace: Delete files
When macher-install is called, these tools are registered with gptel but not activated. The
@macher and @macher-ro presets activate the appropriate subsets.
You can also enable/disable them directly from the gptel menu, load them in restored gptel sessions,
etc. Just make sure you call (macher-enable) or activate @macher-base - this adds a prompt
transform that provides tools with a shared macher-context for outgoing requests.
To customize the available tools and presets, modify macher-tools or macher-presets-alist.
You can see customizable variables/sub-groups with M-x customize-group RET macher.
| Variable | Description |
|---|---|
macher-actions-alist |
Defines available actions (implement, revise, discuss, etc.) |
macher-action-buffer-ui |
UI style for action buffers: 'default, 'org, 'basic, or nil |
macher-action-buffer-setup-hook |
Hook run when creating action buffers |
macher-action-dispatch-hook |
Hook run when invoking an action |
macher-before-action-functions |
Functions run before sending the request |
macher-after-action-functions |
Functions run after request completes |
| Variable | Description |
|---|---|
macher-workspace-functions |
Functions to determine the workspace for a buffer |
macher-workspace-types-alist |
Defines workspace types (project, file, etc.) |
macher-context-string-function |
Generates workspace context information for requests |
macher-context-string-placeholder |
System prompt placeholder string for dynamic context injection |
macher-context-string-marker-start |
Start marker for context in system prompt |
macher-context-string-marker-end |
End marker for context in system prompt |
macher-context-string-max-files |
Max files to list in workspace context |
| Variable | Description |
|---|---|
macher-process-request-function |
What to do when a request completes (shows a patch buffer by default) |
macher-patch-prepare-functions |
Generate patch content (diff, metadata, etc.) |
macher-patch-buffer-ui |
Patch buffer UI: 'diff or nil |
macher-patch-buffer-setup-hook |
Hook run when creating patch buffers |
macher-patch-ready-hook |
Hook run when patch is ready to display |
| Variable | Description |
|---|---|
macher-tools |
Tool definitions for reading/editing files |
macher-presets-alist |
Preset definitions (macher, macher-ro, etc.) |
macher-tool-category |
Category for macher tools in gptel registry |
macher-match-max-columns |
Max line length for search results |