For the longest time I wanted to try the Org + Babel mode of writing my configuration file, and 2024 seemed like a good year to start doing that.
All Emacs files require some comments that identify what the file is for and all that crap.
;;; -*- lexical-binding: t; -*-
;;; init.el --- My Emacs configuration
;;; Commentary:
;;; Code:This file is read by Emacs before anything else. It shouldn’t depend on any package or frame configurations. Used mostly for basic initializations.
;; Set some sane defaults
(setopt ring-bell-function 'ignore ; visual bell
visible-bell t
frame-title-format '("%b")
use-dialog-box t
use-file-dialog nil
use-short-answers t ; no more yes-or-no
x-stretch-cursor t
require-final-newline t
inhibit-splash-screen t
inhibit-startup-screen t
inhibit-x-resources t
inhibit-startup-echo-area-message user-login-name
inhibit-startup-buffer-menu t)
;; Indentation
(setopt standard-indent 2
tab-always-indent 'complete)
(setq-default tab-width 4)
(setq-default indent-tabs-mode nil)
;; I don't want some UI elements enabled, so let's disable them.
(when (display-graphic-p)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(global-hl-line-mode -1))
(delete-selection-mode t)
(global-auto-revert-mode 1) ; reload files when changedWe don’t want Emacs performing too many GC runs on startup, to make it boot as fast as possible.
(setopt gc-cons-threshold most-positive-fixnum
gc-cons-percentage 0.5)
(defvar inkel/emacs--file-name-handler-alist file-name-handler-alist)
(defvar inkel/emacs--vc-handled-backends vc-handled-backends)
(setopt file-name-handler-alist nil
vc-handled-backends inkel/emacs--vc-handled-backends)
(defun inkel/emacs--startup-hook ()
"Restore defaults and report initialization time and GC collections."
(setopt gc-cons-threshold (* 1000 1000 8)
gc-cons-percentage 0.1
file-name-handler-alist inkel/emacs--file-name-handler-alist
vc-handled-backends inkel/emacs--vc-handled-backends)
(message "Emacs ready in %.2fs with %d garbage collections."
(float-time (time-subtract after-init-time before-init-time))
gcs-done))
(add-hook 'emacs-startup-hook #'inkel/emacs--startup-hook)
;; force GC run
(add-hook 'after-init-hook #'garbage-collect t)I don’t want my installed packages to be initialized automatically, as sometimes I install packages just to test them.
(setopt package-enable-at-startup nil)The Go team released nice fonts which are the ones I’ve been using for a long time:
(condition-case nil
(set-face-attribute 'default nil :family "Go Mono" :height 140))I want my Emacs to be maximized, and that upon restart it doesn’t create a new frame (maybe this was a bug?)
;; Start Emacs in full screen mode
(add-hook 'window-setup-hook 'toggle-frame-fullscreen t)
(advice-add 'restart-emacs :before 'toggle-frame-fullscreen)This will make some things below easier
(eval-and-compile
(defsubst emacs-path (path)
(expand-file-name path user-emacs-directory)))
(defconst user-data-directory (emacs-path "data"))
(defun user-data (dir)
"Expands DIR filename within `user-data-directory'."
(expand-file-name dir user-data-directory))Some binaries are installed outside the typical PATH locations, let’s use the one that we have in ~/.bashrc:
(use-package exec-path-from-shell
:ensure t
:if (memq window-system '(mac ns))
:config (exec-path-from-shell-initialize));; Disable the damn thing by making it disposable.
(setopt custom-file (make-temp-file "emacs-custom-"))(setopt make-backup-files nil
backup-inhibited nil
create-lockfiles nil);; Make native compilation silent and prune its cache.
(when (native-comp-available-p)
(setq-default native-comp-async-report-warnings-errors 'silent
native-compile-prune-cache t))(setopt help-window-select t)(use-package savehist
:unless noninteractive
:custom
(savehist-additional-variables '(file-name-history
kmacro-ring
compile-history
compile-command))
(savehist-ignored-variables '(load-history
flyspell-auto-correct-ring
org-roam-node-history
magit-revision-history
org-read-date-history
query-replace-history
yes-or-no-p-history
kill-ring))
(savehist-autosave-interval 60)
(savehist-file (user-data "history"))
(savehist-mode t)
:config
(savehist-mode 1))
(use-package saveplace
:unless noninteractive
:custom
(save-place-file (user-data "places"))
:config
(save-place-mode 1))(use-package ibuffer
:bind (("C-x C-b" . ibuffer))
:config
(setopt ibuffer-default-sorting-mode 'filename/process));;; https://protesilaos.com/codelog/2024-02-08-emacs-window-rules-display-buffer-alist/
(setopt display-buffer-alist
'(
("\\*xref\\*"
(display-buffer-reuse-mode-window display-buffer-below-selected)
(dedicated . t)
(window-height . fit-window-to-buffer))
))(require 'package)
(when (not (package-installed-p 'use-package))
(package-install 'use-package))
(defvar use-package-enable-imenu-support t)
(require 'use-package)
(add-hook 'package-menu-mode-hook #'hl-line-mode)
(setopt package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("org" . "https://orgmode.org/elpa/")
("melpa" . "https://melpa.org/packages/"))
use-package-always-ensure t
use-package-always-defer nil
use-package-verbose init-file-debug
use-package-expand-minimally (not init-file-debug)
debug-on-error init-file-debug)
(setopt package-archive-priorities
'(("melpa" . 3)
("org" . 2)
("gnu" . 1)))
(defvar use-package-enable-imenu-support t)(use-package vundo)(use-package vertico
:ensure t
:config
(setopt vertico-cycle t
vertico-resize nil)
(vertico-mode 1))(use-package marginalia
:ensure t
:config
(marginalia-mode 1))(use-package orderless
:ensure t
:config
(setopt completion-styles '(orderless basic)))Not really minibuffer only, but great addition.
(use-package consult
:hook (completion-list-mode . consult-preview-at-point-mode)
:bind (("M-y" . consult-yank-pop) ;; Better yank?
;; M-g bindings in `goto-map'
("M-g g" . consult-goto-line)
;; ("M-g M-g" . consult-goto-line)
("M-s M-b" . consult-buffer)
("M-s M-f" . consult-find)
("M-g o" . consult-outline)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)))
;; This is useful for navigating through xref history
;; https://takeonrules.com/2024/06/08/adding-a-consult-function-for-visualizing-xref/
(defvar consult--xref-history nil
"History for the `consult-recent-xref' results.")
(defun consult-recent-xref (&optional markers)
"Jump to a marker in MARKERS list (defaults to `xref--history'.
The command supports preview of the currently selected marker position.
The symbol at point is added to the future history."
(interactive)
(consult--read
(consult--global-mark-candidates
(or markers (flatten-list xref--history)))
:prompt "Go to Xref: "
:annotate (consult--line-prefix)
:category 'consult-location
:sort nil
:require-match t
:lookup #'consult--lookup-location
:history '(:input consult--xref-history)
:add-history (thing-at-point 'symbol)
:state (consult--jump-state)))Corfu is a great package enhancing in-buffer completion in an interactive way.
(use-package corfu
:custom
(corfu-cycle t)
(corfu-auto t)
(corfu-auto-prefix 2)
(corfu-auto-delay 0.5)
(corfu-separator ?\s)
(corfu-quit-at-boundary 'separator)
(corfu-echo-documentation 0.25)
(corfu-preview-current 'insert)
(corfu-preselect-first nil)
:commands
(global-corfu-mode corfu-history-mode)
:init
(global-corfu-mode)
(corfu-history-mode));; Glorious package to let you know what binding are available
(use-package which-key
:ensure t
:defer nil
:diminish which-key-mode
:hook (after-init . which-key-mode))(keymap-set global-map "C-z" nil)
(keymap-set global-map "s-q" nil)
(keymap-set global-map "s-p" nil)
(keymap-set global-map "C-x C-p" #'find-file-at-point)
(keymap-unset global-map "s-n")Moving between windows is easier with ace-window:
(use-package ace-window
:ensure t
:bind ("M-o" . ace-window))(use-package imenu
:bind ("s-i" . imenu))And we want to find things using the Silver Seacher:
(use-package ag)(use-package project);; Doom-themes
(use-package doom-themes
:ensure t
:custom
(doom-themes-enable-bold t)
(doom-themes-enable-italic t)
:commands (doom-themes-visual-bell-config
doom-themes-org-config)
:config
;; Enable flashing mode-line on errors
(doom-themes-visual-bell-config)
;; Corrects (and improves) org-mode's native fontification.
(doom-themes-org-config))
(load-theme 'doom-solarized-light t)
;; Losely inspired by http://github.com/LionyxML/auto-dark-emacs
(setopt inkel/current-theme 'light)
(defun inkel/toggle-theme ()
"Set the custom theme."
(interactive)
(cond ((eq inkel/current-theme 'light)
(setopt inkel/current-theme 'dark)
(load-theme 'doom-ayu-mirage t))
((eq inkel/current-theme 'dark)
(setopt inkel/current-theme 'light)
(load-theme 'doom-solarized-light t))))I like editing with knowing which line number am I in. I also would like to know the column number in the modeline.
(use-package display-line-numbers
:config (global-display-line-numbers-mode))
(setopt column-number-mode t)Having trailing whitespace is bad, really bad. Let’s clear those pesky trailing whitespaces when saving.
(add-hook 'before-save-hook #'delete-trailing-whitespace);; Use crux for some useful things, like C-a moving to the first
;; character of the line instead of the beggining of the line!.
;; https://github.com/bbatsov/crux
;; http://pragmaticemacs.com/emacs/open-files-with-the-system-default-application/
;; http://pragmaticemacs.com/emacs/move-to-the-beginning-of-a-line-the-smart-way/
(use-package crux
:ensure t
:bind (("C-c M-o" . crux-open-with)
("C-k" . crux-kill-and-join-forward)
("C-x 4 t" . crux-transpose-windows)
("C-x D" . crux-delete-file-and-buffer)
("C-x M-r" . crux-rename-file-and-buffer)
("s-k" . crux-kill-whole-line)
("C-a" . crux-move-beginning-of-line)))(use-package multiple-cursors
:bind (("C-S-c C-S-c" . mc/edit-lines)
("C->" . mc/mark-next-like-this)
("C-S->" . mc/unmark-next-like-this)
("C-<" . mc/mark-previous-like-this)
("C-S-<" . mc/unmark-previous-like-this)
("C-c C->" . mc/mark-all-like-this)
("C-S-<mouse-1>" . mc/add-cursor-on-click)))These are useful and simple to use, so let’s use them!
(use-package yasnippet
:hook
(prog-mode . yas-minor-mode)
:defines (yas-snippet-dirs)
:commands (yas-reload-all)
:bind
(("C-c y n" . yas-new-snippet)
("C-c y v" . yas-visit-snippet-file)
("C-c y i" . yas-insert-snippet))
:config
(yas-reload-all)
(setopt yas-snippet-dirs
'("~/.emacs.d/snippets")))At Grafana Labs we have a nice script to cleanup merged branches when using squash commits for merging (yuck)
#!/usr/bin/env bash
# set -x
BRANCH=$(git symbolic-ref --short HEAD)
PRIMARY_BRANCH='master'
if [ -n "$(git branch -l main)" ]; then
PRIMARY_BRANCH=main
fi
XARGS_FLAGS="-r"
if [[ $(uname -s) = "Darwin" ]]; then XARGS_FLAGS=""; fi
if [ "$BRANCH" != "$PRIMARY_BRANCH" ]; then
git checkout "$PRIMARY_BRANCH"
fi
git pull
git remote | xargs -n1 git remote prune
git branch --merged | grep -v $PRIMARY_BRANCH | grep -v '^[*+]' | xargs $XARGS_FLAGS git branch -d
git for-each-ref refs/heads/ "--format=%(refname:short)" | while read branch; do mergeBase=$(git merge-base $PRIMARY_BRANCH $branch) && [[ $(git cherry $PRIMARY_BRANCH $(git commit-tree $(git rev-parse $branch\^{tree}) -p $mergeBase -m _)) == "-"* ]] && git branch -D $branch; done
if [ "$BRANCH" != "$PRIMARY_BRANCH" -a -n "$(git branch -l $BRANCH)" ]; then
git checkout "$BRANCH"
fi(defun inkel/magit--log-edit-mode-hook ()
"Editing options for writing commit messages in Magit."
(setopt fill-column 72)
(flyspell-mode t)
(turn-on-auto-fill))
(defun inkel/magit-cleanup ()
"Run git cleanup"
(interactive)
(magit-run-git-async "cleanup")
(magit-process-buffer))
(use-package magit
:bind (("C-x g" . magit-status))
:custom
(magit-bind-magit-project-status t)
(magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1)
(magit-bury-buffer-function #'magit-restore-window-configuration)
:config
;; Speeding up magit-status
;; https://jakemccrary.com/blog/2020/11/14/speeding-up-magit/
(remove-hook 'magit-status-headers-hook 'magit-insert-tags-header) ;; There are ways to see this when needed, most of the time I don't need it.
(remove-hook 'magit-status-headers-hook 'magit-insert-diff-filter-header)
(remove-hook 'magit-status-headers-hook 'magit-insert-upstream-branch-header)
(magit-add-section-hook 'magit-status-sections-hook
'magit-insert-unpushed-to-upstream
'magit-insert-unpushed-to-upstream-or-recent
'replace) ;; Hide "Recent Commits"
(transient-append-suffix 'magit-run "p"
'("C" "Cleanup Git" inkel/magit-cleanup))
(add-hook 'magit-log-edit-mode-hook 'inkel/magit--log-edit-mode-hook))
;; (use-package magit-file-icons
;; :ensure t
;; :after magit
;; :init
;; (magit-file-icons-mode 1)
;; :custom
;; ;; These are the default values:
;; (magit-file-icons-enable-diff-file-section-icons t)
;; (magit-file-icons-enable-untracked-icons t)
;; (magit-file-icons-enable-diffstat-icons t))
(use-package forge :after magit)
(use-package git-link
:bind ("C-c g l" . git-link-dispatch)
:config (require 'git-link-transient))If I write a shell script I want Emacs to make it an executable file if it contains a hash-bang.
(add-hook 'after-save-hook
'executable-make-buffer-file-executable-if-script-p)Flymake is Emacs native and what Eglot uses.
(global-set-key (kbd "s-n") #'flymake-goto-next-error)
(global-set-key (kbd "s-p") #'flymake-goto-prev-error)
(global-set-key (kbd "M-g f") #'flymake-show-buffer-diagnostics)
(use-package sideline-flymake
:hook (flymake-mode . sideline-mode)
:init
(setopt sideline-flymake-display-mode 'point) ; 'point to show errors only on point
; 'line to show errors on the current line
(setopt sideline-backends-right '(sideline-flymake)))(use-package eglot
:ensure t
:custom
(eglot-autoshutdown t)
:commands (eglot-format-buffer)
:bind (:map eglot-mode-map
("C-c C-d" . eldoc)
("C-c C-r" . eglot-rename)
("C-c C-a" . eglot-code-actions)
("s-i" . imenu)))(defvar treesit-language-source-alist '((elisp "https://github.com/Wilfred/tree-sitter-elisp")
(hcl "https://github.com/tree-sitter-grammars/tree-sitter-hcl")
(go "https://github.com/tree-sitter/tree-sitter-go")
(gomod "https://github.com/camdencheek/tree-sitter-gomod")))
(use-package tree-sitter
:custom
(treesit-auto-install-grammar t)
:config
(global-tree-sitter-mode)
(add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))
(use-package tree-sitter-langs)One neat feature is code folding, that is, the ability to hide/display regions based on the programming language.
(use-package fringe-helper)
(use-package ts-fold
:load-path "/Users/inkel/dev/emacs-tree-sitter/ts-fold"
:bind ("C-c C-f" . ts-fold-toggle)
:config
(add-to-list 'ts-fold-range-alist
(cons 'go-ts-mode (assoc 'go-mode ts-fold-range-alist)))
(use-package ts-fold-indicators
:load-path "/Users/inkel/dev/emacs-tree-sitter/ts-fold"
:config
(setopt ts-fold-indicators-fringe 'right-fringe))
;; enable wherever tree-sitter is enabled
(global-ts-fold-mode 1)
)One thing I do all the time in Go is browsing the documentation of a package in pkg.go.dev, so I made a helper to allow me to quickly browse to the package documentation of any of the imports in the current buffer:
(defun inkel/go-file-imports (filename)
"Return a list of the current imports in Go FILENAME.
The returnes list is sorted by stdlib first and third-party then."
(cl-flet ((stdlib-p (pkg) (not (string-match-p "\\." pkg))))
(let ((imports (process-lines "go" "list" "-f" "{{range .Imports}}{{println .}}{{end}}" filename)))
(seq-concatenate 'list
(sort (seq-filter #'stdlib-p imports) #'string-lessp)
(sort (seq-remove #'stdlib-p imports) #'string-lessp)))))
(defun inkel/go-package-docs (&optional package)
"Open up Go package PACKAGE documentation in pkg.go.dev."
(interactive (list (let ((packages (inkel/go-file-imports buffer-file-name))
(vertico-sort-function nil))
(completing-read "Browse pkg.go.dev documentation for: " packages))))
(browse-url (concat "https://pkg.go.dev/" package)))In order to have project.el properly understand Go projects we need to define how to identify a Go project. This is done by traversing the current directory and its parents until it finds a go.mod file.
(defun project-find-go-module (dir)
"Find DIR first parent directory defining a go.mod file."
(when-let* ((root (locate-dominating-file dir "go.mod")))
(cons 'go-module root)))
(cl-defmethod project-root ((project (head go-module)))
"Return root directory of the current Go PROJECT."
(cdr project))
(add-hook 'project-find-functions #'project-find-go-module)Running tests is integral to development, and these helpers should make it a bit easier.
(use-package gotest-mode
:ensure nil
:bind ("s-t" . gotest-dwim)
:load-path "~/repos/gotest-mode.el/"
; :vc (:url "https://github.com/inkel/gotest-mode.el" :rev :newest)
:hook (go-ts-mode . gotest-mode))Because we are using Tree-sitter we have to use the go-ts-mode package. We also add some hooks to organize the imports and format the buffer every time we save.
(defun inkel/eglot--go-organize-imports-on-save ()
"Hook for organizing Go imports when saving buffer."
(call-interactively 'eglot-code-action-organize-imports))
(defun inkel/eglot--go-format-buffer-on-save ()
"Hook for Go projects when saving buffer."
(add-hook 'before-save-hook #'inkel/eglot--go-organize-imports-on-save)
(add-hook 'before-save-hook #'eglot-format-buffer -10 t))
(use-package go-ts-mode
:ensure t
:custom (go-ts-mode-indent-offset tab-width)
:mode "\\.go"
:bind (:map go-ts-mode-map
;("C-c C-t" . inkel/go-test)
("C-c M-p" . inkel/go-package-docs))
:hook ((go-ts-mode . eglot-ensure)
(go-ts-mode . inkel/eglot--go-format-buffer-on-save))
:config
(add-to-list 'tree-sitter-major-mode-language-alist '(go-ts-mode . go))
(setq-default eglot-workspace-configuration
'((:gopls . ((staticcheck . t)
(matcher . "CaseSensitive")))))
(add-to-list 'major-mode-remap-alist '(go-mode . go-ts-mode)))(use-package go-dlv)Yet Another Mistaken Language
(use-package yaml-ts-mode
:mode ("\\.yml" "\\.yaml"))(use-package dockerfile-mode)(defun inkel/org--edit-options ()
"Enable truncate lines and word wrapping on Org files."
(toggle-truncate-lines nil)
(toggle-word-wrap t))
(use-package org
:pin org
:ensure t
:bind (("C-c a" . org-agenda)
("C-c c" . org-capture)
("C-c b" . org-switchb)
("C-c l" . org-store-link))
:custom
(org-directory (expand-file-name "~/org"))
(org-imenu-depth 7)
;; Do not indent lines according to node level.
(org-adapt-indentation nil)
;; Do not ask for confirmation when evaluating source blocks.
(org-babel-config-evaluate nil)
(org-confirm-babel-evaluate nil)
;; do not create a popup when editing a source block
(org-src-window-setup 'current-window)
(org-capture-templates '(("t" "TODO" entry (file+headline org-default-nodes-file "Tasks")
"* TODO %?\n %i\n %a")
("j" "Journal" entry (file+datetree "journal.org")
"* %?\nEntered on %U\n %^g%i")
("o" "On-Call" item (file+datetree "on-call.org")
"")
))
;; Make org the default mode when starting Emacs
(initial-major-mode 'org-mode)
(initial-scratch-message "#\n# Temporary buffer for notes\n#\n")
:config
;; do not create a popup when editing a source block
;;(setq org-src-window-setup 'current-window)
;; enable inserting structured templates easily
(add-to-list 'org-modules 'org-tempo)
(add-hook 'org-mode-hook #'inkel/org--edit-options))As part of my job I usually write commands that feed onto other commands but I’d like to see the intermediate results or having better options for parsing their output. Following Literate DevOps practices makes my life much easier and nicer. And for this, I need some some languages availabel in Babel.
(use-package org
:commands (org-edit-src-exit)
:config
(org-babel-do-load-languages 'org-babel-load-languages
'((emacs-lisp . t)
(awk . t)
(shell . t))))Editing code blocks is great, but I keep pressing C-x C-s to exit them instead of C-c '; this should fix it:
(eval-after-load 'org-src
'(define-key org-src-mode-map
(kbd "C-x C-s") #'org-edit-src-exit))Last but not list, I interact lots with GitHub, so let’s allow Org to export to GitHub Flavored Markdown:
(use-package ox-gfm
:ensure t
:after org)(use-package terraform-mode
:config (setopt terraform-format-on-save t)
:hook ((terraform-mode . eglot-ensure)
(before-save . terraform-format-buffer)))I haven’t done any of this yet, but if I ever do (and I would like to), this is a neat package that will show which keys & commands got executed on the mode line:
(use-package keycast
:config
(keycast-mode-line-mode -1))I have some configurations that are mostly only work related. I should probably move this to a module, but for now, let’s have them here.
For some things we use Jsonnet, which is an awful language, but what can I do?
(setopt js-indent-level 2)
(add-to-list 'treesit-language-source-alist
'(json5 "https://github.com/Joakker/tree-sitter-json5"))
(use-package json5-ts-mode
:mode "\\.json5"
:custom (json5-ts-mode-indent-offset 2))
(defun inkel/jsonnet-lib ()
(let
((dtroot (locate-dominating-file buffer-file-name "ksonnet")))
(if dtroot
(progn
(setopt jsonnet-library-search-directories
(list (expand-file-name "ksonnet/lib" dtroot)))))))
(use-package jsonnet-mode
:hook (jsonnet-mode . inkel/jsonnet-lib)
:custom (jsonnet-indent-level 2))
;; Jsonnet LSP
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(jsonnet-mode . ("jsonnet-language-server" "--tanka"))))In order to use project.el I need it to instruct how to identify the project, which should be simply a directory, but because I use Git worktrees extensively, it’s a good idea to tell project.el how to find them automatically:
(defun project-find-dt (dir)
"Find DIR first parent directory with setup-context."
(when-let* ((root (locate-dominating-file dir "setup-context")))
(cons 'gdt-root root)))
(cl-defmethod project-root ((project (head gdt-root)))
"Return root directory of the current deployment_tools PROJECT."
(cdr project))
(add-hook 'project-find-functions #'project-find-dt)Yes, it’s time to accept that AI is not important. At work I’ve been trying Claude Code, so let’s start with it:
;; Claude Code IDE integration with Emacs
(add-to-list 'vc-handled-backends 'Git)
(use-package vterm)
(use-package claude-code-ide
:ensure t
:vc (:url "https://github.com/manzaltu/claude-code-ide.el" :rev :newest)
:bind ("C-c C-'" . claude-code-ide-menu)
:config
; Enable Emacs MCP tools - https://github.com/manzaltu/claude-code-ide.el#emacs-mcp-tools
(claude-code-ide-emacs-tools-setup))(defun inkel/claude-find-skill ()
"Open a Claude skill directory from ~/.claude/skills/ using completion."
(interactive)
(let* ((skills-dir (expand-file-name "~/.claude/skills/"))
(skills (when (file-directory-p skills-dir)
(seq-filter
(lambda (f)
(file-directory-p (expand-file-name f skills-dir)))
(directory-files skills-dir nil "^[^.]"))))
(skill (completing-read "Claude skill: " skills nil t))
(skill-path (expand-file-name (concat skill "/SKILL.md") skills-dir)))
(find-file skill-path)))
(keymap-set global-map "C-c K" #'inkel/claude-find-skill)I might also try the Pi coding agent, so let’s see how it integrates:
(use-package md-ts-mode)
(use-package pi-coding-agent
:bind ("C-c p" . pi-coding-agent-toggle))Sometimes I want to open up a file from my terminal directly into my main Emacs frame, let’s start a server for it:
(server-start)As with the preface, let’s add the final lines.
(provide 'init)
;;; init.el ends here;; Local Variables: ;; eval: (add-hook ‘after-save-hook ‘org-babel-tangle nil t) ;; End: