claudeck 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,312 +0,0 @@
1
- import { Router } from "express";
2
- import { readFileSync, writeFileSync } from "fs";
3
- import { configPath } from "../../server/paths.js";
4
-
5
- const router = Router();
6
-
7
- const LINEAR_API = "https://api.linear.app/graphql";
8
- const CONFIG_FILE = configPath("linear-config.json");
9
-
10
- // ── Config helpers ──────────────────────────────────────────
11
-
12
- function loadConfig() {
13
- try {
14
- return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
15
- } catch {
16
- return { enabled: false, apiKey: "", assigneeEmail: "" };
17
- }
18
- }
19
-
20
- function saveConfig(cfg) {
21
- writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n");
22
- }
23
-
24
- function getApiKey() {
25
- const cfg = loadConfig();
26
- return cfg.enabled ? cfg.apiKey : "";
27
- }
28
-
29
- function getAssigneeEmail() {
30
- return loadConfig().assigneeEmail || "";
31
- }
32
-
33
- function maskKey(key) {
34
- if (!key || key.length < 8) return key ? "****" : "";
35
- return key.slice(0, 8) + "****" + key.slice(-4);
36
- }
37
-
38
- // ── Config routes ───────────────────────────────────────────
39
-
40
- router.get("/config", (req, res) => {
41
- const cfg = loadConfig();
42
- res.json({ ...cfg, apiKey: maskKey(cfg.apiKey) });
43
- });
44
-
45
- router.put("/config", (req, res) => {
46
- try {
47
- const { enabled, apiKey, assigneeEmail } = req.body;
48
- if (typeof enabled !== "boolean") {
49
- return res.status(400).json({ error: "enabled must be a boolean" });
50
- }
51
-
52
- const existing = loadConfig();
53
- const finalKey = apiKey && !apiKey.includes("****") ? apiKey : existing.apiKey;
54
-
55
- saveConfig({
56
- enabled,
57
- apiKey: finalKey,
58
- assigneeEmail: assigneeEmail || "",
59
- });
60
-
61
- res.json({ ok: true });
62
- } catch (err) {
63
- res.status(500).json({ error: err.message });
64
- }
65
- });
66
-
67
- router.post("/test", async (req, res) => {
68
- const cfg = loadConfig();
69
- const apiKey = cfg.apiKey;
70
- if (!apiKey) {
71
- return res.status(400).json({ ok: false, error: "Linear API key not configured" });
72
- }
73
-
74
- try {
75
- const response = await fetch(LINEAR_API, {
76
- method: "POST",
77
- headers: { "Content-Type": "application/json", Authorization: apiKey },
78
- body: JSON.stringify({ query: `query { viewer { id name email } }` }),
79
- });
80
-
81
- if (!response.ok) {
82
- const text = await response.text();
83
- return res.status(response.status).json({ ok: false, error: text });
84
- }
85
-
86
- const data = await response.json();
87
- if (data.errors) {
88
- return res.json({ ok: false, error: data.errors[0].message });
89
- }
90
-
91
- const viewer = data.data?.viewer;
92
- res.json({ ok: true, user: viewer });
93
- } catch (err) {
94
- res.status(500).json({ ok: false, error: err.message });
95
- }
96
- });
97
-
98
- // ── GraphQL queries ─────────────────────────────────────────
99
-
100
- const TEAMS_QUERY = `
101
- query { teams { nodes { id name } } }
102
- `;
103
-
104
- const TEAM_STATES_QUERY = `
105
- query($id: String!) {
106
- team(id: $id) {
107
- states {
108
- nodes { id name type }
109
- }
110
- }
111
- }
112
- `;
113
-
114
- const USER_BY_EMAIL_QUERY = `
115
- query($email: String!) {
116
- users(filter: { email: { eq: $email } }) {
117
- nodes { id }
118
- }
119
- }
120
- `;
121
-
122
- const CREATE_ISSUE_MUTATION = `
123
- mutation($title: String!, $teamId: String!, $description: String, $stateId: String, $assigneeId: String) {
124
- issueCreate(input: { title: $title, teamId: $teamId, description: $description, stateId: $stateId, assigneeId: $assigneeId }) {
125
- success
126
- issue { id identifier title url }
127
- }
128
- }
129
- `;
130
-
131
- const ISSUES_QUERY = `
132
- query MyIssues {
133
- viewer {
134
- assignedIssues(
135
- filter: {
136
- state: { type: { nin: ["completed", "canceled"] } }
137
- }
138
- orderBy: updatedAt
139
- first: 50
140
- ) {
141
- nodes {
142
- id
143
- identifier
144
- title
145
- url
146
- priority
147
- priorityLabel
148
- dueDate
149
- state { name type color }
150
- labels { nodes { name color } }
151
- }
152
- }
153
- }
154
- }
155
- `;
156
-
157
- router.get("/issues", async (req, res) => {
158
- const apiKey = getApiKey();
159
- if (!apiKey) {
160
- return res.json({ issues: [], error: "Linear not configured" });
161
- }
162
-
163
- try {
164
- const response = await fetch(LINEAR_API, {
165
- method: "POST",
166
- headers: { "Content-Type": "application/json", Authorization: apiKey },
167
- body: JSON.stringify({ query: ISSUES_QUERY }),
168
- });
169
-
170
- if (!response.ok) {
171
- const text = await response.text();
172
- return res.status(response.status).json({ issues: [], error: text });
173
- }
174
-
175
- const data = await response.json();
176
- if (data.errors) {
177
- return res.json({ issues: [], error: data.errors[0].message });
178
- }
179
-
180
- const issues = data.data?.viewer?.assignedIssues?.nodes || [];
181
- res.json({ issues });
182
- } catch (err) {
183
- console.error("Linear API error:", err.message);
184
- res.status(500).json({ issues: [], error: err.message });
185
- }
186
- });
187
-
188
- router.get("/teams", async (req, res) => {
189
- const apiKey = getApiKey();
190
- if (!apiKey) {
191
- return res.json({ teams: [], error: "Linear not configured" });
192
- }
193
-
194
- try {
195
- const response = await fetch(LINEAR_API, {
196
- method: "POST",
197
- headers: { "Content-Type": "application/json", Authorization: apiKey },
198
- body: JSON.stringify({ query: TEAMS_QUERY }),
199
- });
200
-
201
- if (!response.ok) {
202
- const text = await response.text();
203
- return res.status(response.status).json({ teams: [], error: text });
204
- }
205
-
206
- const data = await response.json();
207
- if (data.errors) {
208
- return res.json({ teams: [], error: data.errors[0].message });
209
- }
210
-
211
- const teams = data.data?.teams?.nodes || [];
212
- res.json({ teams });
213
- } catch (err) {
214
- console.error("Linear API error:", err.message);
215
- res.status(500).json({ teams: [], error: err.message });
216
- }
217
- });
218
-
219
- router.get("/teams/:teamId/states", async (req, res) => {
220
- const apiKey = getApiKey();
221
- if (!apiKey) {
222
- return res.json({ states: [], error: "Linear not configured" });
223
- }
224
-
225
- try {
226
- const response = await fetch(LINEAR_API, {
227
- method: "POST",
228
- headers: { "Content-Type": "application/json", Authorization: apiKey },
229
- body: JSON.stringify({
230
- query: TEAM_STATES_QUERY,
231
- variables: { id: req.params.teamId },
232
- }),
233
- });
234
-
235
- if (!response.ok) {
236
- const text = await response.text();
237
- return res.status(response.status).json({ states: [], error: text });
238
- }
239
-
240
- const data = await response.json();
241
- if (data.errors) {
242
- return res.json({ states: [], error: data.errors[0].message });
243
- }
244
-
245
- const allStates = data.data?.team?.states?.nodes || [];
246
- const states = allStates.filter((s) =>
247
- ["unstarted", "started", "completed"].includes(s.type)
248
- );
249
- res.json({ states });
250
- } catch (err) {
251
- console.error("Linear API error:", err.message);
252
- res.status(500).json({ states: [], error: err.message });
253
- }
254
- });
255
-
256
- router.post("/issues", async (req, res) => {
257
- const apiKey = getApiKey();
258
- if (!apiKey) {
259
- return res.status(400).json({ success: false, error: "Linear not configured" });
260
- }
261
-
262
- const { title, description, teamId, stateId } = req.body;
263
- if (!title || !teamId) {
264
- return res.status(400).json({ success: false, error: "title and teamId are required" });
265
- }
266
-
267
- try {
268
- let assigneeId;
269
- const assigneeEmail = getAssigneeEmail();
270
- if (assigneeEmail) {
271
- const userRes = await fetch(LINEAR_API, {
272
- method: "POST",
273
- headers: { "Content-Type": "application/json", Authorization: apiKey },
274
- body: JSON.stringify({ query: USER_BY_EMAIL_QUERY, variables: { email: assigneeEmail } }),
275
- });
276
- if (userRes.ok) {
277
- const userData = await userRes.json();
278
- assigneeId = userData.data?.users?.nodes?.[0]?.id;
279
- }
280
- }
281
-
282
- const response = await fetch(LINEAR_API, {
283
- method: "POST",
284
- headers: { "Content-Type": "application/json", Authorization: apiKey },
285
- body: JSON.stringify({
286
- query: CREATE_ISSUE_MUTATION,
287
- variables: { title, teamId, description: description || undefined, stateId: stateId || undefined, assigneeId: assigneeId || undefined },
288
- }),
289
- });
290
-
291
- if (!response.ok) {
292
- const text = await response.text();
293
- return res.status(response.status).json({ success: false, error: text });
294
- }
295
-
296
- const data = await response.json();
297
- if (data.errors) {
298
- return res.json({ success: false, error: data.errors[0].message });
299
- }
300
-
301
- const result = data.data?.issueCreate;
302
- res.json({
303
- success: result?.success || false,
304
- issue: result?.issue || null,
305
- });
306
- } catch (err) {
307
- console.error("Linear API error:", err.message);
308
- res.status(500).json({ success: false, error: err.message });
309
- }
310
- });
311
-
312
- export default router;
@@ -1,43 +0,0 @@
1
- // Web Component: Linear Create Issue Modal
2
- class ClaudeckLinearCreate extends HTMLElement {
3
- connectedCallback() {
4
- this.innerHTML = `
5
- <div id="linear-create-modal" class="modal-overlay hidden">
6
- <div class="modal">
7
- <div class="modal-header">
8
- <h3>Create Issue</h3>
9
- <button id="linear-create-close" class="modal-close">&times;</button>
10
- </div>
11
- <form id="linear-create-form">
12
- <label for="linear-create-title">Title</label>
13
- <input id="linear-create-title" type="text" placeholder="Issue title" required>
14
- <label for="linear-create-desc">Description</label>
15
- <textarea id="linear-create-desc" rows="3" placeholder="Optional description..."></textarea>
16
- <label for="linear-create-team">Project (Team)</label>
17
- <select id="linear-create-team" required>
18
- <option value="">Select a team...</option>
19
- </select>
20
- <label for="linear-create-state">State</label>
21
- <select id="linear-create-state" disabled>
22
- <option value="">Select a team first...</option>
23
- </select>
24
- <div class="modal-actions">
25
- <button type="button" id="linear-create-cancel" class="modal-btn-cancel">Cancel</button>
26
- <button type="submit" id="linear-create-submit" class="modal-btn-save">Create</button>
27
- </div>
28
- </form>
29
- </div>
30
- </div>
31
- `;
32
-
33
- const overlay = this.querySelector('#linear-create-modal');
34
- const closeBtn = this.querySelector('#linear-create-close');
35
-
36
- closeBtn.addEventListener('click', () => overlay.classList.add('hidden'));
37
- overlay.addEventListener('click', (e) => {
38
- if (e.target === overlay) overlay.classList.add('hidden');
39
- });
40
- }
41
- }
42
-
43
- customElements.define('claudeck-linear-create', ClaudeckLinearCreate);