Project-aware multi-file LLM editing for Emacs, based on gptel.
Emacs Lisp
157
65 commits
updated Jul 26, 2026
A project-aware LLM editing toolset for Emacs, built on gptel.
https://github.com/user-attachments/assets/af8bed3d-913b-4365-9f71-85d969810d22
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.
macher is available on MELPA. Example
configuration with use-package:
(use-package macher
:custom
;; The org UI has structured conversations and nice content folding.
(macher-action-buffer-ui 'org)
:hook
;; Set up action buffer behavior to your liking. Alternately, do
;; this more generally in your `gptel-mode-hook'.
(macher-action-buffer-setup
. (lambda ()
;; Auto-scroll responses.
(setq-local window-point-insertion-type t)
;; Wrap lines.
(visual-line-mode 1))
: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 and tools in any gptel request or
buffer.
https://github.com/user-attachments/assets/d6a5c8ee-559e-42ab-a240-381f7187cb06
Built-in presets:
@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. See macher-context-string-placeholder and macher-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 via
macher-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:
macher-context-string-placeholder in the system promptThis 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.
You can also manage macher tools directly from the gptel-menu.
Actions are convenience commands that use macher presets with a specific workflow:
macher-focus-string).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:
M-x macher-implementC-c C-a (apply hunk) or M-x diff-apply-bufferM-x macher-revise if you need changesOther action-related commands:
macher-action: Run any action from the macher-actions-alist.macher-abort: Cancel running action requests for the current workspace.macher-focus-string: Get a description of your current code location. When called
interactively, yanks the description to the kill ring.The actions buffer UI can be customized with macher-action-buffer-ui (see
Customization).
When using the org-mode UI (via macher-action-buffer-ui and/or gptel-default-mode), macher
will set a GPTEL_TOPIC for outgoing actions, so you can continue conversations directly in the
action buffer using gptel-send.
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.
The workspace is determined by macher-workspace-functions. All file operations are restricted to
the current workspace. By default, macher supports:
project: A project.el projectfile: A single non-project fileA directory workspace type is also available for working in arbitrary directories (not managed
by project.el). It is not enabled by default to avoid accidentally exposing broad directories to the
LLM. To enable it, you can do something like:
(setq macher-workspace-functions '(macher-workspace-project macher-workspace-directory))
To add custom workspace types, customize macher-workspace-types-alist and
macher-workspace-functions.
When you send a macher request with tools:
macher-context struct is created - an ephemeral file-editing
environment.macher-process-request-function generates a unified diff.The macher-context is created lazily (only when tools are actually used) and maintains two
versions of each accessed file:
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 filesWhen 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-focus-string | Function to generate description of current code location |
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 |
macher-tool-output-max-length | Max characters any macher tool may return (errors out if exceeded) |
diff-apply-buffer misbehave when the patch contains new or deleted files?diff-mode in Emacs 30.x and below doesn't handle file creations/deletions well. The issue has been
fixed upstream, and the fix will likely be included in the next major Emacs release. In the
meantime, if this case comes up often for you, see
#45 for possible workarounds.
Emacs Lisp
98.7%
Project-aware multi-file LLM editing for Emacs, based on gptel.
Emacs Lisp
157
65 commits
updated Jul 26, 2026
A project-aware LLM editing toolset for Emacs, built on gptel.
https://github.com/user-attachments/assets/af8bed3d-913b-4365-9f71-85d969810d22
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.
macher is available on MELPA. Example
configuration with use-package:
(use-package macher
:custom
;; The org UI has structured conversations and nice content folding.
(macher-action-buffer-ui 'org)
:hook
;; Set up action buffer behavior to your liking. Alternately, do
;; this more generally in your `gptel-mode-hook'.
(macher-action-buffer-setup
. (lambda ()
;; Auto-scroll responses.
(setq-local window-point-insertion-type t)
;; Wrap lines.
(visual-line-mode 1))
: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 and tools in any gptel request or
buffer.
https://github.com/user-attachments/assets/d6a5c8ee-559e-42ab-a240-381f7187cb06
Built-in presets:
@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. See macher-context-string-placeholder and macher-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 via
macher-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:
macher-context-string-placeholder in the system promptThis 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.
You can also manage macher tools directly from the gptel-menu.
Actions are convenience commands that use macher presets with a specific workflow:
macher-focus-string).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:
M-x macher-implementC-c C-a (apply hunk) or M-x diff-apply-bufferM-x macher-revise if you need changesOther action-related commands:
macher-action: Run any action from the macher-actions-alist.macher-abort: Cancel running action requests for the current workspace.macher-focus-string: Get a description of your current code location. When called
interactively, yanks the description to the kill ring.The actions buffer UI can be customized with macher-action-buffer-ui (see
Customization).
When using the org-mode UI (via macher-action-buffer-ui and/or gptel-default-mode), macher
will set a GPTEL_TOPIC for outgoing actions, so you can continue conversations directly in the
action buffer using gptel-send.
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.
The workspace is determined by macher-workspace-functions. All file operations are restricted to
the current workspace. By default, macher supports:
project: A project.el projectfile: A single non-project fileA directory workspace type is also available for working in arbitrary directories (not managed
by project.el). It is not enabled by default to avoid accidentally exposing broad directories to the
LLM. To enable it, you can do something like:
(setq macher-workspace-functions '(macher-workspace-project macher-workspace-directory))
To add custom workspace types, customize macher-workspace-types-alist and
macher-workspace-functions.
When you send a macher request with tools:
macher-context struct is created - an ephemeral file-editing
environment.macher-process-request-function generates a unified diff.The macher-context is created lazily (only when tools are actually used) and maintains two
versions of each accessed file:
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 filesWhen 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-focus-string | Function to generate description of current code location |
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 |
macher-tool-output-max-length | Max characters any macher tool may return (errors out if exceeded) |
diff-apply-buffer misbehave when the patch contains new or deleted files?diff-mode in Emacs 30.x and below doesn't handle file creations/deletions well. The issue has been
fixed upstream, and the fix will likely be included in the next major Emacs release. In the
meantime, if this case comes up often for you, see
#45 for possible workarounds.
Emacs Lisp
98.7%