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
|