Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,20 @@ On first use, ~mxp~ boots a lightweight TCP eval server inside your running Emac
The server starts automatically - no manual ~M-x~ step required. If the socket is unavailable (e.g., bash built without ~/dev/tcp~ support), ~mxp~ falls back to ~emacsclient~ transparently.

#+attr_html: :width 50%
| Variable | Default | Description |
|---------------+---------+-----------------------------------------------|
| ~MXP_PORT~ | ~17394~ | TCP port for the eval server (localhost only) |
| ~MXP_NO_SOCKET~ | (unset) | Set to ~1~ to force ~emacsclient~-only mode |
| Variable | Default | Description |
|-------------------+---------+-----------------------------------------------------|
| ~MXP_PORT~ | ~17394~ | TCP port for the eval server (localhost only) |
| ~MXP_NO_SOCKET~ | (unset) | Set to ~1~ to force ~emacsclient~-only mode |
| ~EMACS_SOCKET_NAME~ | (unset) | Named socket for ~emacsclient -s~ (e.g., daemon name) |

You can also pass ~-s~ / ~--socket-name~ on the command line:

#+begin_src shell
echo "hello" | mxp -s myserver "*my-buffer*"
mxp --socket-name myserver --from "*my-buffer*"
#+end_src

This is useful when the Emacs daemon was started with a specific socket name, e.g., ~emacs --daemon=myserver~.

To stop the server manually:

Expand Down
1 change: 1 addition & 0 deletions changelog.org
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
* [0.6.0] - 2026-04-06

** Added
- ~-s~ / ~--socket-name~ flag and ~EMACS_SOCKET_NAME~ env var for connecting to a named Emacs daemon socket ([[https://github.com/agzam/mxp/issues/1][#1]])
- Persistent TCP socket transport between ~mxp~ and Emacs
- Auto-bootstrapping: ~mxp~ starts a lightweight eval server inside Emacs on first use (no manual ~M-x~ required)
- All communication goes over a single persistent connection per invocation via bash's ~/dev/tcp~
Expand Down
25 changes: 20 additions & 5 deletions mxp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CHUNK_SIZE=100
STREAM_TIMEOUT=0.3
MXP_PORT="${MXP_PORT:-17394}"
MXP_SOCK_FD=""
EMACS_SOCKET_NAME="${EMACS_SOCKET_NAME:-}"

usage() {
cat << EOF
Expand All @@ -31,6 +32,7 @@ OPTIONS:
-f, --from BUFFER Read from buffer (name or regex) and output to stdout
-a, --append Append to buffer instead of replacing content
-p, --prepend Prepend to buffer (insert at the beginning)
-s, --socket-name NAME Connect to Emacs daemon socket NAME
-F, --force Overwrite existing buffer (skip conflict resolution)
-h, --help Show this help message
-v, --version Show version
Expand Down Expand Up @@ -85,6 +87,15 @@ die() {
exit 1
}

# Wrapper for emacsclient - passes -s flag when socket name is set
run_emacsclient() {
if [ -n "$EMACS_SOCKET_NAME" ]; then
command emacsclient -s "$EMACS_SOCKET_NAME" "$@"
else
command emacsclient "$@"
fi
}

# Find the actual script location, resolving symlinks
find_script_location() {
local script="$0"
Expand Down Expand Up @@ -284,7 +295,7 @@ MXP_ELISP
elisp="${elisp//MXP_PORT_PLACEHOLDER/$port}"
local encoded
encoded=$(printf '%s' "$elisp" | encode_for_elisp)
emacsclient --eval \
run_emacsclient --eval \
"(eval (read (decode-coding-string (base64-decode-string \"$encoded\") 'utf-8)))" \
>/dev/null 2>&1 || return 1
sleep 0.1
Expand Down Expand Up @@ -343,7 +354,7 @@ emacs_eval() {
mxp_disconnect
fi
# Fallback: "$code" preserves embedded quotes as literal characters
emacsclient --eval "$code" 2>/dev/null
run_emacsclient --eval "$code" 2>/dev/null
}

# Verify Emacs is reachable and set up the best transport
Expand All @@ -355,7 +366,7 @@ init_emacs_connection() {
mxp_ensure_connection && return 0
fi
# Socket unavailable - verify emacsclient can reach Emacs
if ! emacsclient --eval "t" &> /dev/null; then
if ! run_emacsclient --eval "t" &> /dev/null; then
die "Cannot connect to Emacs server. Start Emacs daemon with: emacs --daemon"
fi
}
Expand Down Expand Up @@ -473,10 +484,10 @@ open_mode() {
# Open in Emacs (emacsclient -n handles frame management and interactive prompts
# correctly, unlike find-file from a process filter context)
if [ -d "$path" ]; then
emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'"
run_emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'"
echo "Opened directory in Emacs: $path"
else
emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'"
run_emacsclient -n "$path" &>/dev/null || die "Failed to open '$path'"
echo "Opened file in Emacs: $path"
fi
}
Expand Down Expand Up @@ -635,6 +646,10 @@ main() {
-p|--prepend)
prepend="true"
;;
-s|--socket-name)
shift
EMACS_SOCKET_NAME="$1"
;;
-F|--force)
force="true"
;;
Expand Down
59 changes: 59 additions & 0 deletions test-mxp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ else
fail "Version missing"
fi

if $SCRIPT --help | grep -q "\-s, --socket-name"; then
pass "Help shows -s/--socket-name option"
else
fail "Help missing -s/--socket-name option"
fi

# Test 2: Write mode - pipe to new buffer
section "Test 2: Write Mode - Create Buffer"
cleanup_buffer "*test-buffer*"
Expand Down Expand Up @@ -739,6 +745,59 @@ if should_run_test "socket"; then
fi
fi

section "Test 30: Socket Name CLI Flag"
if should_run_test "socket"; then
if [ "$LIST_TESTS" = true ]; then
echo " [socket] Socket name -s flag"
else
# Use -s server (default socket name) with emacsclient fallback to verify
# the flag plumbing works end-to-end
cleanup_buffer "*test-socket-name-flag*"
echo "socket name flag test" | MXP_NO_SOCKET=1 $SCRIPT -s server "*test-socket-name-flag*" &>/dev/null
output=$(MXP_NO_SOCKET=1 $SCRIPT -s server --from "*test-socket-name-flag*" 2>/dev/null)

if [[ "$output" == *"socket name flag test"* ]]; then
pass "Write/read round-trip with -s flag works"
else
fail "Socket name -s flag failed: got '$output'"
fi
fi
fi

section "Test 31: Socket Name Env Var"
if should_run_test "socket"; then
if [ "$LIST_TESTS" = true ]; then
echo " [socket] EMACS_SOCKET_NAME env var"
else
cleanup_buffer "*test-socket-name-env*"
echo "socket name env test" | MXP_NO_SOCKET=1 EMACS_SOCKET_NAME=server $SCRIPT "*test-socket-name-env*" &>/dev/null
output=$(MXP_NO_SOCKET=1 EMACS_SOCKET_NAME=server $SCRIPT --from "*test-socket-name-env*" 2>/dev/null)

if [[ "$output" == *"socket name env test"* ]]; then
pass "Write/read round-trip with EMACS_SOCKET_NAME env var works"
else
fail "EMACS_SOCKET_NAME env var failed: got '$output'"
fi
fi
fi

section "Test 32: Socket Name --socket-name Long Flag"
if should_run_test "socket"; then
if [ "$LIST_TESTS" = true ]; then
echo " [socket] Socket name --socket-name long flag"
else
cleanup_buffer "*test-socket-name-long*"
echo "long flag test" | MXP_NO_SOCKET=1 $SCRIPT --socket-name server "*test-socket-name-long*" &>/dev/null
output=$(MXP_NO_SOCKET=1 $SCRIPT --socket-name server --from "*test-socket-name-long*" 2>/dev/null)

if [[ "$output" == *"long flag test"* ]]; then
pass "Write/read round-trip with --socket-name flag works"
else
fail "Socket name --socket-name long flag failed: got '$output'"
fi
fi
fi

# Summary
section "Test Summary"
echo ""
Expand Down
Loading