{"id":51418778,"url":"https://github.com/agzam/mxp","last_synced_at":"2026-07-04T22:30:34.504Z","repository":{"id":349620667,"uuid":"1086089633","full_name":"agzam/mxp","owner":"agzam","description":"Shell script for piping things in and out of Emacs buffers","archived":false,"fork":false,"pushed_at":"2026-04-06T19:40:43.000Z","size":75,"stargazers_count":28,"open_issues_count":2,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-06T21:15:09.122Z","etag":null,"topics":["emacs","pipe","terminal"],"latest_commit_sha":null,"homepage":"https://raw.githubusercontent.com/agzam/mxp/main/mxp","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agzam.png","metadata":{"files":{"readme":"README.org","changelog":"changelog.org","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-29T23:48:20.000Z","updated_at":"2026-04-06T20:32:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/agzam/mxp","commit_stats":null,"previous_names":["agzam/mxp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/agzam/mxp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agzam%2Fmxp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agzam%2Fmxp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agzam%2Fmxp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agzam%2Fmxp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agzam","download_url":"https://codeload.github.com/agzam/mxp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agzam%2Fmxp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35138074,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["emacs","pipe","terminal"],"created_at":"2026-07-04T22:30:33.761Z","updated_at":"2026-07-04T22:30:34.495Z","avatar_url":"https://github.com/agzam.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: mxp AKA \"Emacs Piper\"\n#+AUTHOR: Ag Ibragimov\n#+DATE: 2025\n#+OPTIONS: toc:t\n[[https://github.com/agzam/mxp/actions/workflows/test.yml][https://github.com/agzam/mxp/actions/workflows/test.yml/badge.svg]]\n\nPipe content between your terminal and Emacs buffers - seamlessly bridge your command-line workflows with your editor.\n\n[[https://www.youtube.com/watch?v=LVlF3-KyqvY][Demo on YouTube, ~12min]]\n\n* What?\n\n~mxp~ is a shell script that acts as a bridge between Unix pipes and Emacs buffers. It supports:\n\n- *Write mode*: Pipe stdin to Emacs buffers (creates new or appends to existing)\n- *Read mode*: Output buffer content to stdout for piping\n- *Streaming*: Real-time content updates with chunked processing\n- *Buffer matching*: Use exact names or regex patterns to find buffers\n- *Auto-generation*: Creates ~*Piper 1*~, ~*Piper 2*~, etc. when no name specified\n- *Conflict handling*: Avoids overwriting buffers unless forced\n- *Persistent socket*: Auto-boots a TCP eval server inside Emacs for fast, ordered communication (falls back to ~emacsclient~ transparently)\n\nThe script works with both bash and zsh, handles special characters properly, and includes comprehensive error handling.\n\n** Do not confuse it with similarly named emacs-piper \n\nHoward Abram's [[https://gitlab.com/howardabrams/emacs-piper][emacs-piper]] project although has similarities, it works differently and has different goals.\n\n* Why?\n\n- Pipe command output directly into Emacs buffers for comfortable viewing\n- Stream logs in real-time to watch them in your editor\n- Extract buffer content from Emacs and pipe it to terminal commands\n\n*** Did you know about piping in Eshell?\n\nEmacs has piping in/out buffers in Eshell! You can pipe content into a buffer like this:\n\n#+begin_src emacs-lisp\n;; In eshell, you can pipe command output to a buffer \n;; You can press \u003cC-c M-b\u003e to pick a buffer instead of typing it\n\ncat file.txt \u003e #\u003cbuffer some-buffer\u003e \n#+end_src\n\nUnfortunately, Eshell doesn't support input redirection, so reading from a buffer ain't so straightforward, yet still possible. You just need to create a custom eshell command to read from a buffer, e.g.:\n\n#+begin_src emacs-lisp\n;; note the eshell/ prefix\n(defun eshell/b (buf-or-regexp)\n  \"Output buffer content of buffer matching BUF-OR-REGEXP.\"\n  (let ((buf (if (bufferp buf-or-regexp)\n                 buf-or-regexp\n               (cl-loop for b in (buffer-list)\n                        thereis (and (string-match-p\n                                      buf-or-regexp (buffer-name b))\n                                     b)))))\n    (when buf\n      (with-current-buffer buf\n        (buffer-substring-no-properties (point-min) (point-max))))))\n#+end_src\n\nAnd then we can use it in Eshell:\n\n#+begin_src emacs-lisp\n;; Press \u003cC-c M-b\u003e to pick a buffer instead of typing it\n\nb #\u003cbuffer README.org\u003e | rg \"error\"\n#+end_src\n\nIncredibly powerful, but this only works *inside* Eshell - you cannot use it in an external terminal. ~mxp~ brings this same workflow to your regular shell, letting you pipe between *any* terminal and Emacs. \n\n* How?\n\n** Make sure you have:\n\n- Emacs with ~emacsclient~ ([[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html][Emacs daemon]] must be running)\n- bash \u003e= 4.0 or zsh\n- Standard Unix utilities (~base64~, ~grep~, ~sed~)\n\n** Install\n\nDownload and put it somewhere in the $PATH\n #+begin_src shell\n curl -fsSL https://raw.githubusercontent.com/agzam/mxp/refs/heads/main/mxp -o \\\n     ~/.local/bin/mxp \\\n     \u0026\u0026 chmod +x ~/.local/bin/mxp \n  #+end_src\n\nMake sure [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html][Emacs daemon]] is running and emacsclient works!\n\n** Socket transport\n\nOn first use, ~mxp~ boots a lightweight TCP eval server inside your running Emacs and connects to it directly via bash's ~/dev/tcp~. All subsequent calls reuse this persistent connection, which means:\n\n- No process spawning per eval (previously each chunk launched a new ~emacsclient~ process)\n- Guaranteed ordering for streamed content (no more background job races)\n- No \"Connection refused\" errors under heavy streaming load\n\nThe 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.\n\n#+attr_html: :width 50%\n| Variable          | Default | Description                                         |\n|-------------------+---------+-----------------------------------------------------|\n| ~MXP_PORT~          | ~17394~   | TCP port for the eval server (localhost only)       |\n| ~MXP_NO_SOCKET~     | (unset) | Set to ~1~ to force ~emacsclient~-only mode             |\n| ~EMACS_SOCKET_NAME~ | (unset) | Named socket for ~emacsclient -s~ (e.g., daemon name) |\n\nYou can also pass ~-s~ / ~--socket-name~ on the command line:\n\n#+begin_src shell\necho \"hello\" | mxp -s myserver \"*my-buffer*\"\nmxp --socket-name myserver --from \"*my-buffer*\"\n#+end_src\n\nThis is useful when the Emacs daemon was started with a specific socket name, e.g., ~emacs --daemon=myserver~.\n\nTo stop the server manually:\n\n#+begin_src emacs-lisp\n(when (and (boundp 'mxp-server-process) (process-live-p mxp-server-process))\n  (delete-process mxp-server-process)\n  (setq mxp-server-process nil))\n#+end_src\n\n** Use\n\n*** Open files and directories in Emacs\n\n#+begin_src shell\n# Open a file\nmxp my-file.txt\nmxp README.org\n\n# Open current directory in dired\nmxp .\n\n# Open any directory\nmxp ~/Projects\nmxp /path/to/directory\n\n# Works with relative paths\nmxp ../other-project/file.txt\n#+end_src\n\n*** Pipe command output into Emacs\n\n#+begin_src shell\n# Pipe to a named buffer\ncat file.txt | mxp \"my-buffer\"\n\n# Pipe to auto-generated buffer (*Piper 1*, *Piper 2*, etc.)\ntail -f /var/log/app.log | mxp\n\n# Append to existing buffer\necho \"more content\" | mxp --append \"my-buffer\"\necho \"more content\" | mxp -a \"my-buffer\"\n\n# Prepend to existing buffer (insert at the top)\necho \"header info\" | mxp --prepend \"my-buffer\"\necho \"header info\" | mxp -p \"my-buffer\"\n\n# Match buffer by regex\necho \"data\" | mxp \"mybuf.*\"\n\n# Force overwrite existing buffer\ncat new.txt | mxp --force \"my-buffer\"\ncat new.txt | mxp -F \"my-buffer\"\n#+end_src\n\n*** Extract buffer content and pipe to commands\n\n#+begin_src shell\n# Output buffer to stdout\nmxp --from \"my-buffer\"\nmxp -f \"my-buffer\"\n\n# Pipe buffer to commands\nmxp --from \"*Messages*\" | grep error\nmxp -f \".*scratch.*\" | wc -l\n\n# Use in command chains\nmxp -f \"my-buffer\" | sort | uniq | less\n#+end_src\n\n*** Process substitution\n\nWorks naturally with process substitution for commands expecting files:\n\n#+begin_src shell\n# Compare two buffers\ndiff \u003c(mxp -f \"version-1\") \u003c(mxp -f \"version-2\")\n\n# Use buffer as input file\njq . \u003c(mxp -f \"*json-data*\")\n#+end_src\n\n** Emacs Hooks\n\nThere are hooks that you can customize:\n\n#+HTML: \u003ctable\u003e\n#+HTML: \u003ctr\u003e\u003cth\u003e\u003c/th\u003e\u003cth\u003eRuns\u003c/th\u003e\u003cth\u003eArgs\u003c/th\u003e\u003cth\u003eNotes\u003c/th\u003e\u003c/tr\u003e\n#+HTML: \u003ctr\u003e\u003ctd\u003e\u003cpre\u003emxp-buffer-hook\u003c/pre\u003e\u003c/td\u003e\u003ctd\u003ewhen the buffer appears\u003c/td\u003e\u003ctd\u003e\u003ccode\u003eBUFFER-NAME\u003c/code\u003e\u003c/td\u003e\u003ctd\u003euseful for setting major mode, etc.\u003c/td\u003e\u003c/tr\u003e\n#+HTML: \u003ctr\u003e\u003ctd\u003e\u003cpre\u003emxp-buffer-update-hook\u003c/pre\u003e\u003c/td\u003e\u003ctd\u003ewhenever there's more data\u003c/td\u003e\u003ctd\u003e\u003ccode\u003eBUFFER-NAME\u003c/code\u003e\u003cbr\u003e\u003ccode\u003eBEG-POS\u003c/code\u003e,\u003ccode\u003eEND-POS\u003c/code\u003e\u003cbr\u003ewhere buffer gets updated\u003c/td\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\n#+HTML: \u003ctr\u003e\u003ctd\u003e\u003cpre\u003emxp-buffer-complete-hook\u003c/pre\u003e\u003c/td\u003e\u003ctd\u003eat the completion\u003c/td\u003e\u003ctd\u003e\u003ccode\u003eBUFFER-NAME\u003c/code\u003e\u003c/td\u003e\u003ctd\u003emay never run for continuous streams\u003c/td\u003e\u003c/tr\u003e\n#+HTML: \u003c/table\u003e\n\nHook examples:\n\n#+begin_src emacs-lisp\n(defun on-mxp-buffer-h (buffer-name)\n  (with-current-buffer buffer-name\n    (when (string-match \".*\\\\.json.*\" buffer-name)\n      (json-mode))))\n(add-hook 'mxp-buffer-hook #'on-mxp-buffer-h)\n\n;; This is how you can re-apply colors. I don't want to make escape\n;; color code processing built into the script itself. It's better to\n;; keep that customizable.\n(defun on-mxp-buffer-update-h (buffer-name beg end)\n  (with-current-buffer buffer-name\n    (ansi-color-apply-on-region beg end)))\n(add-hook 'mxp-buffer-update-hook #'on-mxp-buffer-update-h)\n\n(defun on-mxp-buffer-complete-h (buffer-name)\n  (with-current-buffer buffer-name\n    ;; delete all empty lines\n    (flush-lines \"^$\" (point-min) (point-max))))\n(add-hook 'mxp-buffer-complete-hook #'on-mxp-buffer-complete-h)\n#+end_src\n\n** Usage examples\n\n#+begin_src shell\n# Quick file/directory access\nmxp config.json          \nmxp .                    \nmxp ~/Documents          \nmxp $HOME          \n\n# Watch build logs in Emacs\nnpm run build | mxp \"build-logs\"\n\n# Send curl output to Emacs for inspection\ncurl -s \"https://api.thedogapi.com/v1/breeds\" | jq | mxp \"breeds\"\n# and the the opposite direction:\nmxp \"breeds\" | jq '.[].name' | sort | mxp \"dog names\"\n\n# Extract TODO items from buffer\nmxp -f \"*scratch*\" | grep TODO \u003e todos.txt\n\n# Add timestamps to the top of a log buffer\ndate | mxp --prepend \"logs\"\ntail -f app.log | mxp --append \"logs\"\n\n# Stitch multiple buffers together\ncat \u003c(mxp -f \"header\") \u003c(mxp -f \"body\") | mail -s \"Report\" user@example.com\n\n# Edit a file, then pipe its buffer content through a command\nmxp config.yaml                           # Opens in Emacs\nmxp -f config.yaml | yq '.version' -      # Read it back\n\n# Stream some data with a passtrhough (shows results in both the buffer and terminal)\nping google.com | tee \u003e(mxp)\n#+end_src\n\n\n** [[file:changelog.org][Changelog]] \u0026 [[file:LICENSE][License]]\n\n#+HTML: \u003csmall\u003eCopyright © 2025 Ag Ibragimov \u003cagzam.ibragimov@gmail.com\u003e\u003c/small\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagzam%2Fmxp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagzam%2Fmxp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagzam%2Fmxp/lists"}