Portrait du peintre, 1918 by Henri Matisse. Source: Wikimedia Commons
Warning: this is an experimental, alpha package. It almost certainly has bugs and the interface will change.
Matisse provides a comint-based interface to Claude using shell-maker. This provides a native Emacs experience for interacting with Claude Code, avoiding the issues running Claude inside Emacs terminal emulators like vterm or eat. OTOH matisse does not have all the niceties that the Claude Code CLI provides. Some of these things we can build into matisse; others may prove challenging. We'll see how it goes.
- Emacs 30.1 or later (older versions might work)
- Claude Code CLI installed and accessible
- shell-maker package (version 0.78.2 or later)
- An Anthropic API key
(use-package matisse
:vc (:url "https://github.com/stevemolitor/matisse" :rev :newest))(straight-use-package
'(matisse :type git :host github :repo "stevemolitor/matisse"))Install shell-maker. Then:
-
Clone this repository:
git clone https://github.com/stevemolitor/matisse.git
-
Add to your Emacs configuration:
(add-to-list 'load-path "/path/to/matisse") (require 'matisse)
Set your API key (choose one method):
;; Method 1: Direct configuration (not recommended for public configs)
(setq matisse-api-key "your-api-key-here")
;; Method 2: Using a function
(setq matisse-api-key (lambda () (getenv "ANTHROPIC_API_KEY")))
;; Method 3: Using auth-source (recommended)
;; Add to ~/.authinfo.gpg:
;; machine anthropic.com login apikey password your-api-key-here;; Set the path to Claude Code executable if not in PATH
(setq matisse-claude-code-path "/usr/local/bin/claude")
;; Choose default model
(setq matisse-default-model "claude-sonnet-4-20250514") ; default
;; Set temperature (0.0 to 1.0)
(setq matisse-temperature 0.7)
;; Set max tokens for responses
(setq matisse-max-tokens 4096)
;; Set a system prompt
(setq matisse-system-prompt "You are a helpful assistant.")
;; Disable streaming (not recommended)
(setq matisse-streaming nil)
;; Enable/disable automatic selection context (default: t)
(setq matisse-send-selection-p t)
;; Adjust spinner/blink speed (default: 0.15 seconds)
(setq matisse-spinner-interval 0.2) ; Slower blinking
(setq matisse-spinner-interval 0.1) ; Faster blinkingMatisse automatically tracks your text selections and cursor position, providing Claude with context about what code you're working on. This feature is enabled by default and works seamlessly in the background.
- Automatic Detection: Matisse monitors your selections across all file buffers (but not matisse shell buffers)
- Smart Context: When you send a message to Claude, your current selection context is automatically appended
- VSCode-Style References: Selection context is formatted as
@/path/to/file.txt#L5:10-L9:25(line:character positions) - Mode-line Display: The matisse mode-line shows your current context:
🤖 in matisse.el- when cursor is positioned in a file🤖 2 lines selected- when text is selected🤖⠋ in matisse.el- with spinner animation during requests
;; Enable selection context tracking (default: t)
(setq matisse-send-selection-p t)
;; Disable selection context tracking
(setq matisse-send-selection-p nil)When you select code and send a message to Claude:
Your message here
@/Users/steve/repos/matisse/matisse.el#L528:15-L538:20 - (defun matisse--update-mode-line ()
"Update the mode line with current spinner state and selection info."
(let* ((spinner-part (if matisse--waiting-for-response
(concat " 🤖" (nth matisse--spinner-index matisse--spinner-chars))
" 🤖"))
(selection-part (matisse--format-selection-status)))
(setq matisse--mode-line-format
(if selection-part
(concat spinner-part " " selection-part)
spinner-part))
(force-mode-line-update t)))
Claude receives both your message and the selected code context, making it easier to provide targeted assistance.
Matisse adds spinner to the mode line when waiting for a response from Claude. If you have a custom modeline configuration and you don't see the spinner, you can add it to your modeline:
(setq-default
mode-line-format
(list
;; matisse lighter
'(:eval (when (bound-and-true-p matisse-mode) matisse--mode-line-format))
;; the rest of your modeline format...
))M-x matisse-shell
This opens a new Matisse buffer where you can interact with Claude Code.
- Type your message and press
RETto send - Use shift-return to enter a newline without sending.
exit- Exit the matisse session and close its buffer- Type
help- Show available commands - Type
clear- Clear the conversation C-c C-c- Interrupt current requestC-c C-o- Clear the bufferC-x C-s- Save session transcriptM-p- Cycle backwards through input history
The matisse buffer is a regular Emacs buffer, so things like M-/ (dabbrev-expand), marking, selecting, etc work like normal.
;; Switch models interactively
M-x matisse-set-model
;; Set temperature
M-x matisse-set-temperatureThe Matisse shell inherits key bindings from comint-mode and adds:
RET- Send input to ClaudeS-RET- Insert newline without sendingC-c C-c- Interrupt current generationC-c C-o- Clear the bufferC-x C-s- Save session transcriptM-p- Previous input from historyM-n- Next input from historyC-c C-r- Search input history
Matisse provides real-time visibility into Claude's internal operations through smart progress indicators:
Tool Usage Indicators: See what Claude is doing in real-time
- 📖 Reading README.md...
- ✏️ Editing config.json...
- 💻 Running npm install...
- 🔍 Searching for "function"...
File Change Summaries: Get notified when files are modified
- ✅ Updated README.md
- ✅ File written successfully
Performance Metrics: Track timing, cost, and token usage
- ⏱️ Completed in 12.3s, $0.045, 342 tokens
;; Enable/disable progress indicators (default: t)
(setq matisse-show-progress-indicators t)
;; Enable/disable file change summaries (default: t)
(setq matisse-show-file-changes t)
;; Enable/disable performance summaries (default: nil)
(setq matisse-show-performance-summary nil)
;; Configure icon display mode (default: 'ascii)
;; Options: 'emoji (emoji icons), 'nerd-icons (Nerd Font icons), 'ascii (simple "-")
(setq matisse-progress-icons-mode 'nerd-icons)
;; Adjust icon size (default: 1.0)
(setq matisse-icons-scale-factor 1.2);; Toggle progress display options
M-x matisse-toggle-progress-indicators
M-x matisse-toggle-file-changes
M-x matisse-toggle-performance-summary
M-x matisse-toggle-progress-iconsIf you use ef-themes, you can integrate Matisse progress icon colors with your theme's color palette. This ensures the icons adapt automatically when you switch between ef-themes:
(defun my-matisse-ef-themes-integration ()
"Customize Matisse progress icon faces using ef-themes colors."
(ef-themes-with-colors
(custom-set-faces
;; File operations
`(matisse-nerd-icon-read-face ((,c :foreground ,blue-cooler)))
`(matisse-nerd-icon-write-face ((,c :foreground ,green)))
`(matisse-nerd-icon-edit-face ((,c :foreground ,yellow-warmer)))
`(matisse-nerd-icon-multiedit-face ((,c :foreground ,yellow)))
;; Command and search operations
`(matisse-nerd-icon-bash-face ((,c :foreground ,magenta)))
`(matisse-nerd-icon-grep-face ((,c :foreground ,red-cooler)))
`(matisse-nerd-icon-glob-face ((,c :foreground ,red-warmer)))
;; Task and web operations
`(matisse-nerd-icon-task-face ((,c :foreground ,cyan)))
`(matisse-nerd-icon-webfetch-face ((,c :foreground ,blue)))
`(matisse-nerd-icon-todowrite-face ((,c :foreground ,magenta-cooler)))
;; Status indicators
`(matisse-nerd-icon-success-face ((,c :foreground ,green-warmer)))
`(matisse-nerd-icon-performance-face ((,c :foreground ,cyan-warmer)))
`(matisse-nerd-icon-default-face ((,c :foreground ,fg-dim)))
)))
;; Hook the function to ef-themes
(add-hook 'ef-themes-post-load-hook #'my-matisse-ef-themes-integration)
;; Set progress icons mode to see the colors
(setq matisse-progress-icons-mode 'nerd-icons)You can also manually customize individual icon faces:
(custom-set-faces
'(matisse-nerd-icon-read-face ((t :foreground "#6A9FB5")))
'(matisse-nerd-icon-write-face ((t :foreground "#90A959")))
'(matisse-nerd-icon-edit-face ((t :foreground "#D4843E")))
;; ... customize other faces as needed
)
## Buffer Display Configuration
To position Matisse buffers in a side window (recommended for a better coding workflow), add this to your Emacs configuration:
```elisp
(add-to-list 'display-buffer-alist
'("^\\*matisse"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.33)
(no-delete-other-windows . t)))This configuration:
- Opens Matisse buffers in a side window on the right side of the frame
- Sets the width to 33% of the frame width
- Prevents the window from being deleted when using
delete-other-windows - Keeps your main coding buffers visible while interacting with Claude
Matisse using the Claude Code SDK with streaming JSON input and output
- User Input: Your messages are formatted as JSON lines and sent to the Claude Code process
- Streaming Output: Claude's responses are streamed back as JSON objects
- Real-time Display: Responses are parsed and displayed in real-time as they arrive
- Session Management: The shell-maker framework handles history, transcripts, and buffer management
User messages are sent in this format:
{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Your message here"}]}}Claude Code streams back various JSON message types:
System messages (initialization and other system events):
{"type":"system","subtype":"init","session_id":"..."}Assistant messages (Claude's responses and tool usage):
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Response text"},{"type":"tool_use","name":"Read","input":{"file_path":"..."}}]}}User messages (tool results from Claude's internal tool operations):
{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"...","content":"..."}]}}Result messages (completion with performance metrics):
{"type":"result","duration_ms":1234,"total_cost_usd":0.045,"usage":{"output_tokens":342}}Matisse parses these messages and displays:
- Progress indicators with icons (📖 Reading file.txt...)
- File change summaries (✅ Updated config.json)
- Performance metrics (⏱️ Completed in 12.3s, $0.045, 342 tokens)
Ensure Claude Code is installed and accessible:
which claudeIf not in PATH, set the full path:
(setq matisse-claude-code-path "/full/path/to/claude")Verify your API key is correctly configured:
M-: (matisse--get-api-key)If the Claude process becomes unresponsive, start a new session:
M-x matisse-shellEnable logging for troubleshooting:
(setq shell-maker-logging t)Check the log buffer: *matisse-log*
- Support --continue
- Support --resume
- compact, clear
- Matisse session management
- Commands to send context to Claude
- Hook support, or at least a note in the README explaining how to create hooks using
emacsclient --eval {elisp} - MCP server support, to implement at least some of the IDE integration that tools Monet provide
- Diff display of changes
- Support pasting images
- better permissions checking, perhaps by using a custom permissions prompt tool
- and more…
Contributions are welcome! Please feel free to submit issues or pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built on top of shell-maker by Álvaro Ramírez
- Inspired by chatgpt-shell
- Powered by Claude from Anthropic
Steve Molitor

