diff options
| author | Szymon Szukalski <szymon@szymonszukalski.com> | 2026-04-01 15:39:17 +1100 |
|---|---|---|
| committer | Szymon Szukalski <szymon@szymonszukalski.com> | 2026-04-01 15:39:17 +1100 |
| commit | 08bfb73d73d0b0d63a93a341704d16ef8e3d6cab (patch) | |
| tree | c7ed62958ce10b2a514de1696ad4094975f935cd | |
initial commit
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | AGENTS.md | 41 | ||||
| -rw-r--r-- | README.md | 125 | ||||
| -rw-r--r-- | config.org | 642 |
4 files changed, 814 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6106eb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Keep the repository focused on the literate source and project docs. +/* +!/.gitignore +!/AGENTS.md +!/README.md +!/config.org
\ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f41e7d5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# AGENTS.md + +## Project Purpose + +This repository is for the creation and maintenance of Emacs 30 configuration files. + +## Repository Layout + +- `config.org` is the single hand-edited source of truth for the configuration. +- `init.el` and `early-init.el` are generated from `config.org` and should not be edited directly. +- Structure the literate config by startup responsibility first, then by workflow domain. +- The current Org workflow lives under `~/org/` with `daily/`, `projects/`, `areas/`, `areas/people/`, `resources/`, and `archives/`. +- Daily notes stay as plain Org files under `~/org/daily/`; longer-lived notes are handled through Denote in the same root. +- Agenda files are discovered dynamically from the project, area, and resource directories rather than from a fixed file list. +- The config also includes a small gptel workflow that uses GitHub Copilot as the backend. +- Do not treat `auto-save-list/` as source content. + +## Editing Expectations + +- Prefer small, focused changes over broad rewrites. +- Edit `config.org` first, then regenerate the derived files instead of patching generated output by hand. +- Preserve existing Emacs Lisp style and naming where patterns already exist. +- Avoid unrelated refactors while working on a specific configuration task. +- Keep narrative prose close to the configuration it explains, especially around the note-taking workflow and startup behavior. +- Be explicit about GUI-versus-terminal behavior. If a change affects `emacs -nw`, avoid moving terminal UI changes earlier in startup unless that timing is intentional. +- If the literate file starts to lose coherence, improve its sectioning and prose before introducing new generated helper files. + +## Validation Expectations + +- For Emacs Lisp changes, tangle `config.org` and verify the generated configuration loads cleanly before claiming completion. +- When Emacs is available, prefer a batch check such as `emacs --batch -Q --load init.el` from the repository root. +- Keep regression checks aligned with the generated startup path rather than any retired hand-maintained module layout. +- For changes that affect terminal Emacs behavior, verify in an actual `emacs -nw` session as well as batch mode; batch load alone will not catch interactive tty regressions. + +## Documentation Expectations + +- Document non-obvious conventions close to the relevant configuration in `config.org`. +- Update `README.md` whenever configuration or workflow changes alter package usage, startup behavior, keybindings, directory layout, capture flow, or other documented behavior. +- `README.md` must describe the current configuration truthfully. Do not leave stale documentation behind and do not document planned behavior as current behavior. +- Update AGENTS.md when the repo workflow, Org layout, or verification expectations materially change. +- Keep AGENTS.md concise and update it only when the repository structure or working rules actually change.
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..66c87e4 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# Emacs Configuration + +This repository contains a literate Emacs configuration built around Org mode, Denote, a PARA-style note layout, and a small completion stack. The hand-edited source is `config.org`; `init.el` and `early-init.el` are generated from it. + +## Emacs Setup + +### Source of truth and generated files + +`config.org` is the only file intended for manual configuration edits. The generated startup files are: + +- `early-init.el` for settings that must exist before the first GUI frame. +- `init.el` for the main runtime configuration. + +Both generated files are tangled from `config.org` and should be treated as build artifacts. + +### Package bootstrap + +The config bootstraps packages with built-in `package.el` and uses `use-package` for declaration and load order. Package archives are configured with GNU, NonGNU ELPA, and MELPA, with GNU given highest priority. + +### Core packages and built-in modules + +The current setup uses these packages and built-in modules: + +- `org` and `org-capture` for agenda, capture, daily notes, and the literate configuration itself. +- `denote` for durable notes, naming, keywords, and linking. +- `vertico` for minibuffer completion UI. +- `orderless` for flexible completion matching. +- `marginalia` for minibuffer annotations. +- `gptel` with the GitHub Copilot backend for chat and rewrite workflows inside Emacs. +- `dired` with a macOS-safe `ls` configuration. +- `time` for the modeline clock. +- `modus-themes`, using `modus-vivendi` in the current config. + +### Org mode and note layout + +The note system lives under `~/org/` and is organized like this: + +- `daily/` for plain daily Org files. +- `projects/` for project notes. +- `areas/` for area notes. +- `areas/people/` for people-related notes. +- `resources/` for reference material. +- `archives/` for archived notes. + +This is a PARA-style layout, but the agenda is intentionally narrower than the full tree. Agenda files are discovered dynamically from `projects/`, `areas/`, and `resources/` only. Daily notes and archives are excluded from the agenda scan. + +### Completion setup + +The minibuffer stack is intentionally small: + +- `vertico` provides the completion UI. +- `orderless` handles matching. +- `marginalia` adds annotations. + +### Babel tangle process + +The literate config uses Org Babel to generate the runtime files. Most Emacs Lisp blocks inherit `:tangle init.el` from the file header, while early-startup blocks explicitly tangle to `early-init.el`. + +To regenerate the generated files from the repo root: + +```sh +emacs --batch -Q --eval '(progn (require (quote ob-tangle)) (org-babel-tangle-file "config.org"))' +``` + +To verify that the generated main config still loads: + +```sh +emacs --batch -Q --load ./init.el +``` + +## Workflow + +### Daily notes + +Daily notes are plain Org files in `~/org/daily/`, named by date. When a daily note is created through the config, it starts with these headings: + +- `Tasks` +- `Meetings` +- `Notes` +- `Open loops` + +This keeps daily capture fast without routing everything through Denote. + +### Agenda usage + +The agenda is opened through `ss/open-agenda`, bound to `C-c a`. Before running `org-agenda`, the config refreshes `org-agenda-files` from the PARA directories and explicitly loads `org-agenda` so agenda-specific variables are available. + +This means the agenda reflects the current project, area, and resource files at runtime instead of relying on a fixed file list. + +### Capture flow + +`C-c c` opens capture. The configured templates cover: + +- daily tasks +- daily notes +- daily meetings +- Denote-backed captures for generic notes, projects, areas, people, resources, and meetings + +Denote captures prompt for title, keywords, and subdirectory placement where appropriate, while daily captures write directly into the current day's plain Org file. + +### Note creation and linking + +Denote handles long-lived notes. The main bindings are: + +- `C-c n n` to open or create a Denote note. +- `C-c n l` to insert a Denote link. +- `C-c n m` to create a PARA subdirectory from the minibuffer before capturing into it. +- `C-c n d` to open today's daily note. + +Keyword prompts and directory placement are part of the workflow, not an afterthought. The config is set up so structure is created first, then capture writes into it. + +### Terminal and GUI behavior + +GUI Emacs and terminal Emacs are handled slightly differently. + +- GUI frames get the preferred frame size, font setup, and UI trimming. +- In `emacs -nw`, the menu bar is disabled on `emacs-startup-hook` rather than earlier in startup, because changing that timing too early caused interactive terminal regressions in kitty. + +If you change terminal behavior, test it in a real `emacs -nw` session. Batch load checks are necessary, but they are not enough for tty input and UI behavior. + +## Maintenance Rules + +- Update `config.org` first, then regenerate `init.el` and `early-init.el`. +- Keep this README aligned with the current configuration. If package usage, startup behavior, keybindings, or workflow changes, update this file in the same change. +- Do not document planned behavior as if it already exists.
\ No newline at end of file diff --git a/config.org b/config.org new file mode 100644 index 0000000..1992330 --- /dev/null +++ b/config.org @@ -0,0 +1,642 @@ +#+title: Emacs Configuration +#+startup: overview +#+DATE: 2026-03-24T10:00:00+11:00 +#+DRAFT: false +#+PROPERTY: header-args:emacs-lisp :results silent :tangle init.el + +* Early startup + +These settings have to exist before the first GUI frame is created, so they +tangle into =early-init.el=. + +#+begin_src emacs-lisp :tangle early-init.el +;;; early-init.el --- generated from config.org -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This file is generated from config.org. Do not edit it directly. + +;;; Code: + +(dolist (parameter '((width . 140) + (height . 42))) + (add-to-list 'default-frame-alist parameter) + (add-to-list 'initial-frame-alist parameter)) +#+end_src + +* Bootstrapping + +This is the start of the main runtime entry point, which tangles into =init.el=. + +#+begin_src emacs-lisp +;;; init.el --- generated from config.org -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This file is generated from config.org. Do not edit it directly. + +;;; Code: + +(let ((minver "27.1")) + (when (version< emacs-version minver) + (error "Your Emacs is too old -- this config requires v%s or higher" minver))) +(when (version< emacs-version "28.1") + (message + (concat + "Your Emacs is old, and some functionality in this config will be " + "disabled. Please upgrade if possible."))) +#+end_src + +* Shared paths and system identity + +These definitions set up the shared paths used by the rest of the +configuration, including the Org directory under =~/org/=. + +#+begin_src emacs-lisp + (defconst *spell-check-support-enabled* nil) + (defconst *is-a-windows* (memq system-type '(windows-nt ms-dos cygwin))) + (defconst *is-a-linux* (eq system-type 'gnu/linux)) + (defconst *is-a-mac* (eq system-type 'darwin)) + + (defun ss/home-path (path) + "Expand PATH relative to the user's home directory." + (expand-file-name path "~")) + + (defun ss/config-path (path) + "Expand PATH relative to `user-emacs-directory'." + (expand-file-name path user-emacs-directory)) + + (defun ss/org-path (path) + "Expand PATH relative to the Org directory." + (expand-file-name path (ss/home-path "org/"))) + + (provide 'init-paths) + + ;; Keep custom-set-variables out of the main config. + (setq custom-file (ss/config-path "custom.el")) +#+end_src + +* Package setup + +This section bootstraps packages and defines the archives the rest of the +configuration relies on. + +#+begin_src emacs-lisp + (eval-and-compile + (require 'package) + + (setq package-archives + (append '(("melpa" . "https://melpa.org/packages/")) + package-archives) + package-archive-priorities '(("gnu" . 10) + ("nongnu" . 8) + ("melpa" . 5)) + package-install-upgrade-built-in t + use-package-always-ensure nil) + + (package-initialize) + (require 'use-package)) +#+end_src + +* Interface defaults + +This section sets the visual defaults: theme, fonts, and frame behavior. + +#+begin_src emacs-lisp + (defconst ss/font-family "JetBrains Mono" + "Preferred font family for GUI Emacs.") + + (defconst ss/font-height 160 + "Preferred default font height for GUI Emacs.") + + (defconst ss/font-weight 'medium + "Preferred default font weight for GUI Emacs.") + + (defconst ss/frame-width 140 + "Preferred width for graphical Emacs frames, in columns.") + + (defconst ss/frame-height 42 + "Preferred height for graphical Emacs frames, in lines.") + + (defun ss/apply-frame-size (&optional frame) + "Apply the preferred size to FRAME when it is graphical. + If FRAME is nil, use the selected frame." + (let ((target-frame (or frame (selected-frame)))) + (when (display-graphic-p target-frame) + (set-frame-size target-frame ss/frame-width ss/frame-height)))) + + (defun ss/apply-font-faces () + "Apply the original JetBrains-based face setup." + (set-face-attribute + 'default nil + :family ss/font-family :height ss/font-height :weight ss/font-weight) + (set-face-attribute + 'fixed-pitch nil + :family ss/font-family :weight ss/font-weight) + (set-face-attribute + 'fixed-pitch-serif nil + :family ss/font-family :weight ss/font-weight)) + + (defun ss/disable-menu-bar () + "Disable the menu bar for the current frame/session." + (menu-bar-mode -1)) + + (add-hook 'after-make-frame-functions #'ss/apply-frame-size) + + (when (display-graphic-p) + (ss/apply-frame-size) + (ss/disable-menu-bar) + (tool-bar-mode -1) + (scroll-bar-mode -1) + (tooltip-mode -1) + (when (find-font (font-spec :name ss/font-family)) + (ss/apply-font-faces))) + + (unless (display-graphic-p) + (add-hook 'emacs-startup-hook #'ss/disable-menu-bar)) + + (setq inhibit-startup-message t + inhibit-startup-screen t + ring-bell-function 'ignore) + + (use-package modus-themes + :ensure nil + :no-require t + :config + (load-theme 'modus-vivendi t)) + + (line-number-mode 1) + (column-number-mode 1) + (show-paren-mode 1) + + ;; Disable all fringe indicators + (setq-default indicate-empty-lines nil) + (setq-default indicate-buffer-boundaries nil) + (setq-default fringe-indicator-alist nil) + +#+end_src + +* Modeline + +#+begin_src emacs-lisp + (use-package time + :ensure nil + :config + ;; Enable 24-hour time display without load average. + (setq display-time-24hr-format t + display-time-day-and-date t + display-time-default-load-average nil) + (display-time-mode 1)) + + ;; Customize modeline appearance - white background with line only on top + (set-face-attribute 'mode-line nil + :background "white" + :foreground "black" + :overline "gray50" + :underline nil + :box nil) + + (set-face-attribute 'mode-line-inactive nil + :background "gray95" + :foreground "gray40" + :overline "gray30" + :underline nil + :box nil) + + ;; Customize the modeline format with padding and right-aligned time + (setq-default mode-line-format + (list + ;; Left padding + " " + "%e" + mode-line-front-space + mode-line-mule-info + mode-line-client + mode-line-modified + mode-line-remote + mode-line-frame-identification + mode-line-buffer-identification + " " + mode-line-position + '(vc-mode vc-mode) + " " + mode-line-modes + ;; Right-align from here + '(:eval (propertize " " 'display '((space :align-to (- right 22))))) + mode-line-misc-info + ;; Right padding + " " + mode-line-end-spaces)) + +#+end_src + +* Editing defaults + +This section covers global editing behavior and a few startup-time tuning +choices. + +#+begin_src emacs-lisp + (set-language-environment "UTF-8") + (set-default-coding-systems 'utf-8) + (prefer-coding-system 'utf-8) + + (setq auto-save-default nil + backup-inhibited t + echo-keystrokes 0.1 + compilation-ask-about-save nil + mouse-wheel-scroll-amount '(1 ((shift) . 1)) + mouse-wheel-progressive-speed nil + mouse-wheel-follow-mouse t + scroll-step 1 + scroll-conservatively 101 + enable-recursive-minibuffers t + gc-cons-threshold (* 128 1024 1024) + read-process-output-max (* 4 1024 1024) + process-adaptive-read-buffering nil) + + (fset 'yes-or-no-p 'y-or-n-p) + (global-auto-revert-mode 1) + (delete-selection-mode 1) + + (setq-default indent-tabs-mode nil + fill-column 80 + tab-width 2 + indicate-empty-lines t + sentence-end-double-space nil) + +#+end_src + +* Dired + +On macOS, the system =ls= does not support GNU's =--dired= flag. Keeping +Dired on its built-in path avoids noisy directory listing errors when opening +folders from note helpers. + +#+begin_src emacs-lisp + (use-package dired + :ensure nil + :custom + (dired-use-ls-dired nil)) +#+end_src + +* Minibuffer completion + +This keeps completion close to standard Emacs behaviour while improving the +minibuffer prompts used throughout the notes workflow. Vertico provides the +UI, Orderless handles flexible matching, and Marginalia adds lightweight +annotations. + +#+begin_src emacs-lisp + (use-package vertico + :ensure t + :pin melpa + :init + (vertico-mode 1)) + + (use-package orderless + :ensure t + :pin melpa + :custom + (completion-styles '(orderless basic)) + (completion-category-defaults nil) + (completion-category-overrides '((file (styles basic partial-completion))))) + + (use-package marginalia + :ensure t + :pin melpa + :after vertico + :init + (marginalia-mode 1)) +#+end_src + +* Notes workflow + +This keeps the note-taking system deliberately small. Daily notes stay as plain +Org files in =~/org/daily/=, while longer-lived notes use Denote inside the +same root directory and rely on links for relationships. + +** Org foundations + +The Org setup establishes the shared directories, ensures the PARA structure is +present at startup, and provides a small helper for opening today's daily note +with the standard section layout already in place. Agenda views stay focused on +PARA notes, so project, area, and resource files can surface TODOs without +pulling in daily or archived notes. A small directory helper keeps PARA +subdirectories easy to create from the minibuffer before capturing into them. + +#+begin_src emacs-lisp + (use-package org + :ensure nil + :functions (denote-keywords-prompt) + :defines (denote-directory denote-use-directory denote-use-keywords) + :preface + (defconst ss/org-directory (expand-file-name "~/org/") + "Root directory for Org files.") + + (defconst ss/org-daily-directory (expand-file-name "daily/" ss/org-directory) + "Directory for plain daily Org notes.") + + (defconst ss/org-projects-directory (expand-file-name "projects/" ss/org-directory) + "Directory for project notes.") + + (defconst ss/org-areas-directory (expand-file-name "areas/" ss/org-directory) + "Directory for area notes.") + + (defconst ss/org-people-directory (expand-file-name "areas/people/" ss/org-directory) + "Directory for people notes.") + + (defconst ss/org-resources-directory (expand-file-name "resources/" ss/org-directory) + "Directory for resource notes.") + + (defconst ss/org-archives-directory (expand-file-name "archives/" ss/org-directory) + "Directory for archived notes.") + + (defconst ss/org-note-directories + (list ss/org-directory + ss/org-daily-directory + ss/org-projects-directory + ss/org-areas-directory + ss/org-people-directory + ss/org-resources-directory + ss/org-archives-directory) + "Directories that make up the note-taking workflow.") + + (defconst ss/org-agenda-directories + (list ss/org-projects-directory + ss/org-areas-directory + ss/org-resources-directory) + "Directories whose Org files feed the agenda.") + + (defconst ss/org-subdirectory-roots + `(("projects" . ,ss/org-projects-directory) + ("areas" . ,ss/org-areas-directory) + ("people" . ,ss/org-people-directory) + ("resources" . ,ss/org-resources-directory)) + "PARA roots offered when creating note subdirectories.") + + (defun ss/denote-capture-in-directory (directory &optional keywords &rest prompts) + "Start a Denote Org capture in DIRECTORY with KEYWORDS and PROMPTS. +If PROMPTS is empty, rely on `denote-prompts'." + (let* ((prompt-for-keywords (memq :keywords prompts)) + (denote-directory directory) + (denote-use-directory (unless (memq :subdirectory prompts) directory)) + (denote-use-keywords + (if prompt-for-keywords + (delete-dups (append keywords (denote-keywords-prompt))) + keywords))) + (if prompts + (denote-org-capture-with-prompts + (memq :title prompts) + nil + (memq :subdirectory prompts) + (memq :date prompts) + (memq :template prompts)) + (denote-org-capture)))) + + (defun ss/note-subdirectory-candidates (root) + "Return existing subdirectories under ROOT as relative paths." + (sort + (delete-dups + (mapcar (lambda (path) + (directory-file-name (file-relative-name path root))) + (seq-filter + #'file-directory-p + (directory-files-recursively root directory-files-no-dot-files-regexp t t)))) + #'string<)) + + (defun ss/create-note-subdirectory () + "Create a PARA subdirectory using minibuffer completion." + (interactive) + (let* ((root-name (completing-read + "PARA root: " + (mapcar #'car ss/org-subdirectory-roots) + nil t)) + (root (alist-get root-name ss/org-subdirectory-roots nil nil #'string=)) + (completion-extra-properties '(:category file)) + (subdirectory (completing-read + (format "Subdirectory in %s: " root-name) + (ss/note-subdirectory-candidates root) + nil nil)) + (target (expand-file-name subdirectory root)) + (existing (file-directory-p target))) + (make-directory target t) + (message "%s note directory: %s" + (if existing "Using existing" "Created") + target))) + + (defun ss/ensure-org-note-directories () + "Create the Org directories used by the notes workflow." + (mapc (lambda (directory) + (make-directory directory t)) + ss/org-note-directories)) + + (defun ss/ensure-org-agenda-loaded () + "Load Org agenda support before using agenda-specific helpers. +This ensures `org-agenda-file-regexp' and `org-agenda' are available." + (require 'org-agenda)) + + (defun ss/org-agenda-files () + "Return the Org files that should be scanned by the agenda." + (ss/ensure-org-agenda-loaded) + (delete-dups + (apply #'append + (mapcar (lambda (directory) + (if (file-directory-p directory) + (directory-files-recursively directory org-agenda-file-regexp) + nil)) + ss/org-agenda-directories)))) + + (defun ss/refresh-org-agenda-files () + "Refresh `org-agenda-files' from the current PARA directories." + (setq org-agenda-files (ss/org-agenda-files))) + + (defun ss/daily-note-path (&optional time) + "Return the file name for the daily note at TIME. +If TIME is nil, use the current date." + (expand-file-name + (format-time-string "%Y-%m-%d.org" time) + ss/org-daily-directory)) + + (defun ss/daily-note-template (&optional time) + "Return the initial contents for the daily note at TIME." + (format "#+title: %s\n\n* Tasks\n\n* Meetings\n\n* Notes\n\n* Open loops\n" + (format-time-string "%Y-%m-%d" (or time (current-time))))) + + (defun ss/ensure-daily-note (&optional time) + "Create the daily note for TIME when it does not exist. +Return the path to the note." + (let* ((date (or time (current-time))) + (file (ss/daily-note-path date))) + (unless (file-exists-p file) + (make-directory (file-name-directory file) t) + (with-temp-file file + (insert (ss/daily-note-template date)))) + file)) + + (defun ss/open-todays-note () + "Open today's daily Org note." + (interactive) + (find-file (ss/ensure-daily-note))) + + (defun ss/open-agenda () + "Refresh agenda files and invoke `org-agenda'." + (interactive) + (ss/ensure-org-agenda-loaded) + (ss/refresh-org-agenda-files) + (call-interactively #'org-agenda)) + :bind (("C-c a" . ss/open-agenda) + ("C-c c" . org-capture) + ("C-c n m" . ss/create-note-subdirectory) + ("C-c n d" . ss/open-todays-note)) + :config + (setq org-directory ss/org-directory + org-hide-emphasis-markers t) + (add-hook 'org-mode-hook + (lambda () + (setq-local org-hide-emphasis-markers t) + (font-lock-flush) + (font-lock-ensure))) + (ss/refresh-org-agenda-files) + (add-hook 'org-capture-after-finalize-hook #'ss/refresh-org-agenda-files) + (ss/ensure-org-note-directories)) +#+end_src + +** Capture entry points + +Daily capture goes to today's plain Org file. Denote capture uses Denote's own +Org integration so note identity, metadata, and directories stay under Denote's +control rather than custom code. The convenience templates set a few durable +defaults and prompt for subdirectory placement within the relevant PARA root. + +#+begin_src emacs-lisp + (use-package org-capture + :ensure nil + :after (org denote) + :config + (setq org-capture-templates + `(("d" "Daily") + ("dt" "Task" entry + (file+headline ,#'ss/ensure-daily-note "Tasks") + "* TODO %?") + ("dn" "Note" entry + (file+headline ,#'ss/ensure-daily-note "Notes") + "* %?") + ("dm" "Meeting" entry + (file+headline ,#'ss/ensure-daily-note "Meetings") + "* %<%H:%M> %?") + ("n" "Denote") + ("nn" "Generic" plain + (file denote-last-path) + (function + (lambda () + (denote-org-capture-with-prompts :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t) + ("np" "Project" plain + (file denote-last-path) + (function + (lambda () + (ss/denote-capture-in-directory + ss/org-projects-directory '("project") :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t) + ("na" "Area" plain + (file denote-last-path) + (function + (lambda () + (ss/denote-capture-in-directory + ss/org-areas-directory '("area") :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t) + ("nP" "Person" plain + (file denote-last-path) + (function + (lambda () + (ss/denote-capture-in-directory + ss/org-people-directory '("person") :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t) + ("nr" "Resource" plain + (file denote-last-path) + (function + (lambda () + (ss/denote-capture-in-directory + ss/org-resources-directory '("resource") :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t) + ("nm" "Meeting" plain + (file denote-last-path) + (function + (lambda () + (ss/denote-capture-in-directory + ss/org-directory '("meeting") :title :keywords :subdirectory))) + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t)))) +#+end_src + +** Denote + +Denote manages the durable notes. The folder layout reflects lifecycle, while +Denote handles naming, metadata, linking, and retrieval. + +#+begin_src emacs-lisp + (use-package denote + :ensure t + :after org + :bind (("C-c n n" . denote-open-or-create) + ("C-c n l" . denote-link)) + :config + (setq denote-directory ss/org-directory + denote-known-keywords '("area" "project" "person" "meeting" "1on1" "resource" "decision") + denote-prompts '(title keywords) + denote-org-capture-specifiers "%?") + (denote-rename-buffer-mode 1)) +#+end_src + +* Gptel workflow + +This keeps LLM chat available as a small workflow tool inside Emacs. GitHub +Copilot authentication is handled on demand by gptel, so there is no token +plumbing in this file. + +#+begin_src emacs-lisp + (use-package gptel + :ensure t + :init + (setq gptel-default-mode 'org-mode + gptel-model 'gpt-4o + gptel-backend (gptel-make-gh-copilot "Copilot")) + :bind (("C-c n g" . gptel) + ("C-c n s" . gptel-send) + ("C-c n r" . gptel-rewrite) + ("C-c n a" . gptel-add))) +#+end_src + +* Generated file footers + +The closing blocks just finish the generated startup files cleanly. + +#+begin_src emacs-lisp + +(when (file-exists-p custom-file) + (load custom-file nil 'nomessage)) + +;;; init.el ends here +#+end_src + +#+begin_src emacs-lisp :tangle early-init.el + +;;; early-init.el ends here +#+end_src |
