claude-pager

On <2026-06-03 Wed> I somehow bumped into idea to use emacsclient as EDITOR in Claude Code.

Turns out CC respects EDITOR env variable, either from environment or configured in ~/.claude/settings.json:

{"env": {"EDITOR": "emacsclient", "VISUAL": "emacsclient"}}

Even if [Feature Request] Allow configuring external editor for Ctrl-G prompt editing · Issue #18990 · anthropics/claude-code is still in open.

In that issue I spotted this project: gradigit/claude-pager. Fast C transcript pager for Claude Code: no blank Ctrl-G screen, clickable links/files, queued prompt composer. And its sister editor: gradigit/turbodraft.

Tried both. Turbodraft - I don't need another text editor rather than Emacs, so skipping it. But claude-pager looks useful, so I forked it.

First, I wired Emacs into claude-pager; so that it renders transcripts in both CC screen when it waits for the prompt + adds it to Emacs temporary buffer.

This mostly solves clunkiness of copying from claude terminal into Emacs and back - now I press {C-g} in claude and continue editing in Emacs.

Related to Agent SDK billing and parting ways with agent-shell.

This is how my env block looks like in ~/.claude/settings.json.

{
  "env": {
    "EDITOR": "/Users/pavel/.velppa/claude-pager/bin/claude-pager-open",
    "CLAUDE_PAGER_EDITOR": "/Users/pavel/.velppa/claude-pager/emacs/claude-emacs-prompt",
    "CLAUDE_PAGER_EDITOR_TYPE": "gui"
  }
}

{C-g} in claude opens claude-pager-open that generates transcript and pastes it into both terminal and to the temp file to edit in Emacs, then runs claude-emacs-prompt shell file that calls elisp file which loads into the buffer, assigns keybindings etc.

Setup involves adding hooks:

SessionStart hook:
     26:            "command": "/Users/pavel/.velppa/claude-pager/shim/save-session-transcript.sh"

Then I rewrote the binary from C to Zig. Rewrite used Superpowers to develop a spec first, then launch a series of agents. It took 4+ hours for CC to finish rewrite, it was ready in the morning. It used all extra usage (42 EUR) in fast mode in first 20 minutes.

7000 loc in C => ??? in Zig.

Now I understand the C well.

Another interesting project from the same author:

  • gradigit/confetti - Lightweight confetti animation for macOS — fire colorful confetti from your screen corners

elpa-find-file function

· :Elisp:Emacs:

While researching internals of agent-shell I needed to quickly visit Elisp files by name. Here's the helper, which takes the file name and finds it within installed packages.

Examples:

  • (elpa-find-file "agent-shell.el")
  • (elpa-find-file "avy.el")
(defun elpa-find-file (pattern)
  "Open first file matching PATTERN in the elpa directory.
Error if multiple versions of the same package match."
  (interactive "sFile: ")
  (let* ((elpa-dir (expand-file-name "elpa" user-emacs-directory))
         (files (thread-first
                  "rg --files %s | grep -F \"%s\" | grep -v '\\.elc$'"
                  (format (shell-quote-argument elpa-dir) pattern)
                  shell-command-to-string
                  split-string))
         (pkgs (mapcar (lambda (f)
                         (let ((dir (cadr (split-string f "/elpa/"))))
                           (replace-regexp-in-string
                            "-[0-9].*?/.*\\'" "" dir)))
                       files))
         (dups (seq-filter (lambda (p)
                             (> (cl-count p pkgs :test #'string=) 1))
                           (seq-uniq pkgs))))
    (when dups
      (user-error "Multiple versions found for: %s — clean up %s"
                  (string-join dups ", ") elpa-dir))
    (unless files
      (user-error "file %s not found" pattern))
    (find-file (cl-first files))))
Support to jumping to a :line:column

Okay, almost immediately I wanted something like this - (elpa-find-file "agent-shell.el:4616"), which is essentially what visit-source.el does. But, it turned out better not integrate visit-source, but implement parsing of line directly in the function.

So, version 2:

(defun elpa-find-file (pattern)
  "Open file matching PATTERN in the elpa directory.
PATTERN may include a trailing :LINE or :LINE:COL.
Error if multiple versions of the same package match."
  (interactive "sFile: ")
  (let* ((parts (split-string pattern ":"))
         (name (car parts))
         (line (and (nth 1 parts) (string-to-number (nth 1 parts))))
         (col (and (nth 2 parts) (string-to-number (nth 2 parts))))
         (elpa-dir (expand-file-name "elpa" user-emacs-directory))
         (files (thread-first
                  "rg --files %s | grep -F \"%s\" | grep -v '\\.elc$'"
                  (format (shell-quote-argument elpa-dir) name)
                  shell-command-to-string
                  split-string))
         (pkgs (mapcar (lambda (f)
                         (let ((dir (cadr (split-string f "/elpa/"))))
                           (replace-regexp-in-string
                            "-[0-9].*?/.*\\'" "" dir)))
                       files))
         (dups (seq-filter (lambda (p)
                             (> (cl-count p pkgs :test #'string=) 1))
                           (seq-uniq pkgs))))
    (when dups
      (user-error "Multiple versions found for: %s — clean up %s"
                  (string-join dups ", ") elpa-dir))
    (unless files
      (user-error "file %s not found" name))
    (find-file (cl-first files))
    (when line
      (goto-char (point-min))
      (forward-line (1- line))
      (when col (forward-char (1- col))))))