install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/gh-emacs" ~/.claude/skills/plurigrid-asi-gh-emacs && rm -rf "$T"
manifest:
skills/gh-emacs/SKILL.mdsource content
gh-emacs Skill
Decentralized Emacs: Self-hosted elisp evaluation via GitHub Actions
Core Concept
GitHub Actions as a decentralized Emacs compute fabric:
- Trigger elisp evaluation from local Emacs
- GitHub runners execute
emacs --batch --eval - Results stream back via workflow artifacts or gh CLI
- Self-hosted runners = your own Emacs cloud
┌─────────────────┐ gh workflow run ┌──────────────────┐ │ Local Emacs │ ─────────────────────▶│ GitHub Actions │ │ (controller) │ │ (emacs --batch) │ └────────┬────────┘ └────────┬─────────┘ │ │ │ gh run watch / artifacts │ ◀─────────────────────────────────────────┘
Package Ecosystem
| Package | Stars | Description |
|---|---|---|
| elisp-check | ~50⭐ | GitHub Action for elisp CI |
| actions-batch | 183⭐ | Time-sharing supercomputer on GH Actions |
| consult-gh | 148⭐ | Interactive gh CLI via Consult/Vertico |
| forge | 1.4k⭐ | Work with forges from Magit |
| ghub | 158⭐ | GitHub/GitLab/Gitea API client |
consult-gh: Interactive gh CLI
(use-package consult-gh :after consult :custom (consult-gh-default-clone-directory "~/src/") (consult-gh-show-preview t) :config ;; Enable forge integration (require 'consult-gh-forge) ;; Enable embark actions (require 'consult-gh-embark) ;; Preview with syntax highlighting (setq consult-gh-issue-preview-major-mode 'gfm-mode) (setq consult-gh-file-preview-major-mode 'fundamental-mode))
Core Commands
| Command | Description |
|---|---|
| Fuzzy search repositories |
| Search issues/PRs across repos |
| Search code with preview |
| Clone with completion |
| Fork repository |
| List PRs for current repo |
| List issues for current repo |
Embark Actions
(with-eval-after-load 'embark (defvar-keymap consult-gh-embark-repo-map :doc "Embark actions for consult-gh repos" "c" #'consult-gh-repo-clone "f" #'consult-gh-repo-fork "v" #'consult-gh-repo-view "w" #'consult-gh-repo-browse) (add-to-list 'embark-keymap-alist '(consult-gh-repo . consult-gh-embark-repo-map)))
forge: Magit Integration
(use-package forge :after magit :config ;; Configure forge database (setq forge-database-file (expand-file-name "forge-database.sqlite" user-emacs-directory)) ;; Add GitHub Enterprise hosts ;; (add-to-list 'forge-alist '("github.company.com" "github.company.com/api/v3" ;; "github.company.com" forge-github-repository)) )
Forge Commands
| Binding | Command | Description |
|---|---|---|
| | Fetch issues/PRs from forge |
| | Create PR |
| | Create issue |
| | Browse at point |
| | List issues |
| | List PRs |
ghub: API Client
(use-package ghub :config ;; GraphQL queries (defun gh/viewer-repos () "Fetch current user's repositories via GraphQL." (ghub-graphql "query { viewer { repositories(first: 100) { nodes { nameWithOwner } } } }"))) ;; REST API example (defun gh/repo-topics (owner repo) "Get topics for OWNER/REPO." (ghub-get (format "/repos/%s/%s/topics" owner repo) nil :accept "application/vnd.github.mercy-preview+json"))
Gay.jl Color Integration
(defvar gh-emacs-stream-colors '("#63B6F0" "#89DF91" "#CF6971" "#8E9EDF" "#C25828" "#ED6BDB" "#D1E598") "Stream 3 colors for GitHub operations (Bitter Lesson stream).") (defun gh/colorize-by-state (state) "Return color based on PR/issue STATE." (pcase state ("open" "#89DF91") ; green ("closed" "#CF6971") ; red ("merged" "#8E9EDF") ; purple (_ "#63B6F0"))) ; blue default (defun gh/consult-gh-colorize () "Add Gay.jl colors to consult-gh results." (let ((inhibit-read-only t)) (save-excursion (goto-char (point-min)) (while (re-search-forward "\\[\\(open\\|closed\\|merged\\)\\]" nil t) (let* ((state (match-string 1)) (color (gh/colorize-by-state state))) (put-text-property (match-beginning 0) (match-end 0) 'face `(:foreground ,color :weight bold)))))))
Transient Menu
(require 'transient) (transient-define-prefix gh-emacs-transient () "GitHub CLI interactive commands." ["Search" ("s r" "Repos" consult-gh-search-repos) ("s i" "Issues" consult-gh-search-issues) ("s c" "Code" consult-gh-search-code)] ["Current Repo" ("p l" "PR list" consult-gh-pr-list) ("i l" "Issue list" consult-gh-issue-list) ("p c" "Create PR" forge-create-pullreq :if (lambda () (forge-get-repository nil))) ("i c" "Create issue" forge-create-issue :if (lambda () (forge-get-repository nil)))] ["Actions" ("c" "Clone" consult-gh-repo-clone) ("f" "Fork" consult-gh-repo-fork) ("b" "Browse" forge-browse-dwim)] ["Forge" ("F" "Fetch" forge-fetch) ("L p" "List PRs" forge-list-pullreqs) ("L i" "List Issues" forge-list-issues)]) (global-set-key (kbd "C-c g h") 'gh-emacs-transient)
dwim-shell-command Recipes
(dwim-shell-command-define :name "gh: View PR diff" :command "gh pr diff <<*>>" :utils "gh" :documentation "View PR diff for number at point") (dwim-shell-command-define :name "gh: Create release" :command "gh release create <<*>> --generate-notes" :utils "gh" :documentation "Create release with auto-generated notes") (dwim-shell-command-define :name "gh: Run workflow" :command "gh workflow run <<f>>" :utils "gh" :documentation "Trigger workflow file") (dwim-shell-command-define :name "gh: PR review" :command "gh pr review <<*>> --approve" :utils "gh" :documentation "Approve PR")
Org-mode Integration
(defun gh/org-capture-issue () "Capture new GitHub issue from org-mode." (interactive) (let* ((title (read-string "Issue title: ")) (body (org-export-string-as (buffer-substring-no-properties (region-beginning) (region-end)) 'gfm t))) (shell-command (format "gh issue create --title %s --body %s" (shell-quote-argument title) (shell-quote-argument body))))) (add-to-list 'org-capture-templates '("g" "GitHub Issue" entry (file+headline "~/org/github.org" "Issues") "* TODO %^{Title} :github: :PROPERTIES: :REPO: %^{Repo} :END: %? ** Body " :jump-to-captured t))
chatgpt-shell Integration
(defun gh/ai-review-pr (pr-number) "Use chatgpt-shell to review PR." (interactive "nPR number: ") (let* ((diff (shell-command-to-string (format "gh pr diff %d" pr-number))) (prompt (format "Review this pull request diff for: 1. Potential bugs or issues 2. Code style improvements 3. Security concerns 4. Performance implications ```diff %s ```" diff))) (chatgpt-shell-send-to-buffer prompt))) (defun gh/ai-generate-pr-description () "Generate PR description from staged changes." (interactive) (let* ((diff (shell-command-to-string "git diff --staged")) (prompt (format "Generate a clear PR description for these changes: ```diff %s
Format: Title, Summary, Key Changes (bullet points), Testing Notes" diff))) (chatgpt-shell-send-to-buffer prompt)))
## Workflow: Full Interactive Session
- C-c g h → Open gh-emacs transient
- s r → Search repos (consult-gh-search-repos)
- <select repo> → Embark: c=clone, f=fork, v=view
- After clone:
- N f f → forge-fetch (get issues/PRs)
- N l p → forge-list-pullreqs
- <select PR> → M-x gh/ai-review-pr
- Create PR:
- N c p → forge-create-pullreq
- M-x gh/ai-generate-pr-description
## Neighbor Skills - **xenodium-elisp**: chatgpt-shell, dwim-shell-command integration - **gwern-emacs**: Self-experimentation (track PR review quality) - **gh-skill-explorer**: Discover repos and SKILL.md patterns - **code-review**: Automated code review skill - **gay-mcp**: Deterministic colors for PR/issue states ## Decentralized Elisp Evaluation ### GitHub Actions Workflow Template ```yaml # .github/workflows/elisp-eval.yml name: Elisp Eval on: workflow_dispatch: inputs: elisp_code: description: 'Elisp code to evaluate' required: true type: string emacs_version: description: 'Emacs version' default: '29.4' type: string jobs: eval: runs-on: ubuntu-latest steps: - uses: purcell/setup-emacs@master with: version: ${{ inputs.emacs_version }} - name: Evaluate Elisp id: eval run: | result=$(emacs --batch --eval "${{ inputs.elisp_code }}" 2>&1) echo "result<<EOF" >> $GITHUB_OUTPUT echo "$result" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Upload Result uses: actions/upload-artifact@v4 with: name: elisp-result path: | /tmp/elisp-output.txt
Self-Hosted Runner for Persistent Emacs
# .github/workflows/elisp-self-hosted.yml name: Self-Hosted Elisp on: workflow_dispatch: inputs: elisp_code: description: 'Elisp to evaluate' required: true jobs: eval: runs-on: [self-hosted, emacs] # Your labeled runner steps: - name: Eval with persistent Emacs daemon run: | emacsclient --eval "${{ inputs.elisp_code }}" \ || emacs --batch --eval "${{ inputs.elisp_code }}"
Trigger from Local Emacs
(defun gh-emacs-remote-eval (code &optional repo) "Evaluate CODE on GitHub Actions runner. REPO defaults to current repository." (interactive "sElisp code: ") (let* ((repo (or repo (gh-emacs--current-repo))) (escaped-code (shell-quote-argument code))) (async-shell-command (format "gh workflow run elisp-eval.yml -R %s -f elisp_code=%s" repo escaped-code)) (message "Dispatched to %s - use gh run watch to monitor" repo))) (defun gh-emacs-remote-eval-buffer () "Send current buffer to remote Emacs for evaluation." (interactive) (let ((code (buffer-substring-no-properties (point-min) (point-max)))) (gh-emacs-remote-eval (format "(progn %s)" code)))) (defun gh-emacs-remote-eval-region (start end) "Send region to remote Emacs for evaluation." (interactive "r") (gh-emacs-remote-eval (buffer-substring-no-properties start end)))
Fetch Remote Results
(defun gh-emacs-fetch-result (run-id &optional repo) "Fetch result artifact from RUN-ID." (interactive "nRun ID: ") (let* ((repo (or repo (gh-emacs--current-repo))) (cmd (format "gh run download %d -R %s -n elisp-result -D /tmp/gh-elisp" run-id repo))) (shell-command cmd) (find-file "/tmp/gh-elisp/elisp-output.txt"))) (defun gh-emacs-watch-run (run-id &optional repo) "Watch RUN-ID and fetch result when complete." (interactive "nRun ID: ") (let* ((repo (or repo (gh-emacs--current-repo)))) (async-shell-command (format "gh run watch %d -R %s && gh run download %d -R %s -n elisp-result" run-id repo run-id repo))))
Batch Queue Pattern (actions-batch inspired)
(defvar gh-emacs-batch-queue nil "Queue of elisp jobs to dispatch.") (defun gh-emacs-batch-enqueue (code priority) "Add CODE to batch queue with PRIORITY." (push (list :code code :priority priority :time (current-time)) gh-emacs-batch-queue) (setq gh-emacs-batch-queue (sort gh-emacs-batch-queue (lambda (a b) (> (plist-get a :priority) (plist-get b :priority)))))) (defun gh-emacs-batch-dispatch-all () "Dispatch all queued jobs to GitHub Actions." (interactive) (dolist (job gh-emacs-batch-queue) (gh-emacs-remote-eval (plist-get job :code))) (message "Dispatched %d jobs" (length gh-emacs-batch-queue)) (setq gh-emacs-batch-queue nil))
Self-Hosted Runner Setup
# On your server/container: # 1. Download runner mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64-2.311.0.tar.gz -L \ https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz # 2. Configure with Emacs label ./config.sh --url https://github.com/OWNER/REPO \ --token YOUR_TOKEN \ --labels emacs,elisp-eval # 3. Install Emacs sudo apt-get install emacs # 4. Start persistent daemon emacs --daemon # 5. Run the runner ./run.sh
Gay.jl Integration for Job Coloring
(defvar gh-emacs-job-colors '((queued . "#63B6F0") (in_progress . "#ED6BDB") (completed . "#89DF91") (failure . "#CF6971") (cancelled . "#8E9EDF")) "Colors for job states.") (defun gh-emacs-colorize-job (job-status) "Get color for JOB-STATUS." (alist-get (intern job-status) gh-emacs-job-colors "#D1E598"))
Transient Menu (Updated)
(transient-define-prefix gh-emacs-transient () "Decentralized Emacs via GitHub Actions." ["Remote Eval" ("e e" "Eval expression" gh-emacs-remote-eval) ("e b" "Eval buffer" gh-emacs-remote-eval-buffer) ("e r" "Eval region" gh-emacs-remote-eval-region)] ["Jobs" ("j w" "Watch run" gh-emacs-watch-run) ("j f" "Fetch result" gh-emacs-fetch-result) ("j l" "List runs" gh-emacs-run-list)] ["Batch" ("b q" "Enqueue job" (lambda () (interactive) (gh-emacs-batch-enqueue (read-string "Code: ") (read-number "Priority: " 5)))) ("b d" "Dispatch all" gh-emacs-batch-dispatch-all)] ["Local" ("s r" "Search repos" consult-gh-search-repos :if (lambda () (fboundp 'consult-gh-search-repos))) ("p l" "PR list" gh-emacs-pr-list) ("i l" "Issue list" gh-emacs-issue-list)]) (global-set-key (kbd "C-c g h") 'gh-emacs-transient)
Resources
- elisp-check - GitHub Action for elisp
- actions-batch - Time-sharing on GH Actions
- purcell/setup-emacs - Emacs in Actions
- consult-gh README
- forge manual
- ghub manual
- gh CLI manual
- Self-hosted runners
End-of-Skill Interface
Commands
# gh CLI direct gh repo list --limit 20 gh pr list --state open gh issue create --title "Title" --body "Body" gh run list --limit 10 # Just recipes just gh-search-repos "query" just gh-clone owner/repo just gh-pr-create
Autopoietic Marginalia
The interaction IS the skill improving itself.
Every use of this skill is an opportunity for worlding:
- MEMORY (-1): Record what was learned
- REMEMBERING (0): Connect patterns to other skills
- WORLDING (+1): Evolve the skill based on use
Add Interaction Exemplars here as the skill is used.