#!/usr/bin/env bash # emacsclient-eval: evaluate elisp (argv, or stdin/heredoc) on the Emacs daemon. # # emacsclient-eval '(+ 1 2)' # # emacsclient-eval <<'ELISP' # (+ 1 2) # ELISP # # The Lisp side writes the result to /tmp/ec/ec- and # reports that path back (short, safe to print directly) instead of # using emacsclient's --eval "-print" reply for the result itself - # works around emacsclient garbling large results (see # ~/.claude/projects/-Users-pavel-Notes/memory/project_emacsclient_split_message.md). # # If the result file is 8KB or smaller, this prints its content and # deletes it. Otherwise the file is left in place and this prints its # path instead, for the caller to read separately. # # The code runs inside save-window-excursion + save-excursion, so it # can't leave a different buffer/window layout showing on the selected # frame once it returns (Emacs doesn't redraw mid-call, only at safe # points, so this reverts before anything is ever shown). It does NOT # stop code that explicitly pops open a new frame - Emacs has no # general way to block that from Lisp. set -euo pipefail if [ $# -gt 0 ]; then code="$*"; else code=$(cat); fi max_bytes=8192 # The caller's CLAUDE_PID is visible to the code via (getenv "CLAUDE_PID"). claude_pid=nil if [[ "${CLAUDE_PID:-}" =~ ^[0-9]+$ ]]; then claude_pid="\"$CLAUDE_PID\""; fi outfile=$(TMPDIR="$(getconf DARWIN_USER_TEMP_DIR)" emacsclient --eval " (progn (make-directory \"/tmp/ec\" t) (let ((path (format \"/tmp/ec/ec-%s\" (format-time-string \"%Y%m%dT%H%M%S.%6N\")))) (with-temp-file path (insert (format \"%S\" (save-window-excursion (save-excursion (let ((process-environment (append (and $claude_pid (list (concat \"CLAUDE_PID=\" $claude_pid))) process-environment))) (progn $code ))))))) path)) ") outfile=${outfile#\"} outfile=${outfile%\"} size=$(stat -f%z "$outfile") if [ "$size" -gt "$max_bytes" ]; then echo "$outfile" else cat "$outfile" echo rm -f "$outfile" fi