aboutsummaryrefslogtreecommitdiff
path: root/modules/linear-config.el
blob: 479f72e4487304c14b898d1e3ec0cd4e9163a3c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
;;; linear-config.el --- Linear.app integration -*- lexical-binding: t; -*-
;; author: Craig Jennings <c@cjennings.net>

;;; Commentary:

;; Wires the local linear-emacs checkout (~/code/linear-emacs) into the config,
;; pointed at DeepSat's Linear workspace.
;;
;; Authentication:
;;   The Linear personal API key is read from authinfo.gpg, never plaintext.
;;   Add an entry like:
;;     machine api.linear.app login apikey password lin_api_YOURKEYHERE
;;   Generate the key in Linear: Settings -> Security & access -> Personal API
;;   keys.  The key is loaded lazily on first use, so there is no GPG prompt at
;;   startup.
;;
;; The default team is DeepSat's Software Engineering team (the SE-* issues), so
;; new issues land there unless another team is chosen.
;;
;; Keybindings (C-; L prefix):
;;   C-; L l  — list issues
;;   C-; L p  — list issues by project
;;   C-; L n  — new issue
;;   C-; L s  — enable org sync
;;   C-; L S  — disable org sync
;;   C-; L t  — test connection
;;   C-; L ?  — check setup

;;; Code:

(require 'system-lib)  ;; provides cj/auth-source-secret-value

;; Owned by linear-emacs, which loads lazily via :load-path below.
(defvar linear-emacs-api-key)
(defvar linear-emacs-default-team-id)
(declare-function linear-emacs--graphql-request-async "linear-emacs")

(defconst cj/linear-team-id "9fca2cf6-390c-4102-a9ff-f94a4ed823c5"
  "Linear team id for DeepSat's Software Engineering team (the SE-* issues).")

(defun cj/linear--ensure-api-key ()
  "Load the Linear API key from authinfo.gpg into `linear-emacs-api-key' if unset.
Looks up host \"api.linear.app\".  This is a no-op once the key is set, so the
GPG prompt fires at most once per session and only when Linear is actually used."
  (unless linear-emacs-api-key
    (setq linear-emacs-api-key (cj/auth-source-secret-value "api.linear.app"))))

(defun cj/linear--ensure-key-before (&rest _)
  "Advice: load the Linear API key before a GraphQL request runs.
Named (not a lambda) so the advice is idempotent across reloads and removable."
  (cj/linear--ensure-api-key))

(defun cj/linear--install-key-advice ()
  "Install the lazy API-key loader on every entry point that needs the key.
The GraphQL request funnels all real operations.  `linear-emacs-check-setup'
reads `linear-emacs-api-key' directly without making a request, so it needs the
loader too — otherwise it reports \"not set\" on a fresh session before the key
has ever been fetched."
  (advice-add 'linear-emacs--graphql-request-async :before
              #'cj/linear--ensure-key-before)
  (advice-add 'linear-emacs-check-setup :before
              #'cj/linear--ensure-key-before))

(use-package linear-emacs
  :ensure nil                       ;; local checkout, not from an archive
  :load-path "~/code/linear-emacs"
  :defer t
  :commands (linear-emacs-list-issues
             linear-emacs-list-issues-by-project
             linear-emacs-new-issue
             linear-emacs-enable-org-sync
             linear-emacs-disable-org-sync
             linear-emacs-test-connection
             linear-emacs-check-setup)
  :config
  (setq linear-emacs-default-team-id cj/linear-team-id)
  ;; Keep the synced org file inside emacs home, next to the calendar-sync
  ;; output (gcal.org / pcal.org / dcal.org).  Without this it falls back to
  ;; `org-directory'/gtd/linear.org and silently creates a stray ~/org tree.
  (setq linear-emacs-org-file-path
        (expand-file-name "data/linear.org" user-emacs-directory))
  ;; Load the key lazily before any operation that reads it — both the GraphQL
  ;; request and the check-setup diagnostic.  Retries if the key was added to
  ;; authinfo after a first (failed) attempt this session.
  (cj/linear--install-key-advice))

;; ------------------------------ Keybindings ----------------------------------

(defvar cj/linear-keymap (make-sparse-keymap)
  "Keymap for Linear commands under C-; L.")

(global-set-key (kbd "C-; L") cj/linear-keymap)

(define-key cj/linear-keymap (kbd "l") #'linear-emacs-list-issues)
(define-key cj/linear-keymap (kbd "p") #'linear-emacs-list-issues-by-project)
(define-key cj/linear-keymap (kbd "n") #'linear-emacs-new-issue)
(define-key cj/linear-keymap (kbd "s") #'linear-emacs-enable-org-sync)
(define-key cj/linear-keymap (kbd "S") #'linear-emacs-disable-org-sync)
(define-key cj/linear-keymap (kbd "t") #'linear-emacs-test-connection)
(define-key cj/linear-keymap (kbd "?") #'linear-emacs-check-setup)

;; Register which-key labels lazily so this module's require doesn't depend on
;; which-key being loaded.  Same pattern as the other client modules.
(with-eval-after-load 'which-key
  (which-key-add-keymap-based-replacements cj/linear-keymap
    "" "linear menu"
    "l" "list issues"
    "p" "issues by project"
    "n" "new issue"
    "s" "enable org sync"
    "S" "disable org sync"
    "t" "test connection"
    "?" "check setup"))

(provide 'linear-config)
;;; linear-config.el ends here