@plotday/tool-linear 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/linear.d.ts +88 -0
- package/dist/linear.js +378 -0
- package/dist/linear.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Plot Technologies Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Linear Tool for Plot
|
|
2
|
+
|
|
3
|
+
Sync your Linear teams and issues with Plot.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- OAuth authentication with Linear
|
|
8
|
+
- Sync issues from Linear teams to Plot activities
|
|
9
|
+
- Real-time updates via webhooks
|
|
10
|
+
- Issue comments synced as activity notes
|
|
11
|
+
- Automatic issue state mapping
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Linear } from "@plotday/tool-linear";
|
|
17
|
+
import { Twist, type ToolBuilder } from "@plotday/twister";
|
|
18
|
+
|
|
19
|
+
export default class MyTwist extends Twist<MyTwist> {
|
|
20
|
+
build(build: ToolBuilder) {
|
|
21
|
+
return {
|
|
22
|
+
linear: build(Linear),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async activate() {
|
|
27
|
+
// Request Linear authorization
|
|
28
|
+
const authLink = await this.tools.linear.requestAuth(this.onAuthComplete);
|
|
29
|
+
// ... show authLink to user
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async onAuthComplete(auth: { authToken: string }) {
|
|
33
|
+
// Get available teams
|
|
34
|
+
const projects = await this.tools.linear.getProjects(auth.authToken);
|
|
35
|
+
|
|
36
|
+
// Start syncing a team
|
|
37
|
+
await this.tools.linear.startSync(
|
|
38
|
+
auth.authToken,
|
|
39
|
+
projects[0].id,
|
|
40
|
+
this.onIssue
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async onIssue(issue: NewActivityWithNotes) {
|
|
45
|
+
// Handle synced issue
|
|
46
|
+
await this.tools.plot.createActivity(issue);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, Linear } from "./linear";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/linear.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type ActivityLink, type NewActivityWithNotes } from "@plotday/twister";
|
|
2
|
+
import type { Project, ProjectAuth, ProjectSyncOptions, ProjectTool } from "@plotday/twister/common/projects";
|
|
3
|
+
import { Tool, type ToolBuilder } from "@plotday/twister/tool";
|
|
4
|
+
import { Callbacks } from "@plotday/twister/tools/callbacks";
|
|
5
|
+
import { Integrations } from "@plotday/twister/tools/integrations";
|
|
6
|
+
import { Network } from "@plotday/twister/tools/network";
|
|
7
|
+
import { Plot } from "@plotday/twister/tools/plot";
|
|
8
|
+
import { Tasks } from "@plotday/twister/tools/tasks";
|
|
9
|
+
/**
|
|
10
|
+
* Linear project management tool
|
|
11
|
+
*
|
|
12
|
+
* Implements the ProjectTool interface for syncing Linear teams and issues
|
|
13
|
+
* with Plot activities.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Linear extends Tool<Linear> implements ProjectTool {
|
|
16
|
+
build(build: ToolBuilder): {
|
|
17
|
+
integrations: Promise<Integrations>;
|
|
18
|
+
network: Promise<Network>;
|
|
19
|
+
callbacks: Promise<Callbacks>;
|
|
20
|
+
tasks: Promise<Tasks>;
|
|
21
|
+
plot: Promise<Plot>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Create Linear API client with auth token
|
|
25
|
+
*/
|
|
26
|
+
private getClient;
|
|
27
|
+
/**
|
|
28
|
+
* Request Linear OAuth authorization
|
|
29
|
+
*/
|
|
30
|
+
requestAuth<TCallback extends (auth: ProjectAuth, ...args: any[]) => any>(callback: TCallback, ...extraArgs: TCallback extends (auth: any, ...rest: infer R) => any ? R : []): Promise<ActivityLink>;
|
|
31
|
+
/**
|
|
32
|
+
* Handle successful OAuth authorization
|
|
33
|
+
*/
|
|
34
|
+
private onAuthSuccess;
|
|
35
|
+
/**
|
|
36
|
+
* Get list of Linear teams (projects)
|
|
37
|
+
*/
|
|
38
|
+
getProjects(authToken: string): Promise<Project[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Start syncing issues from a Linear team
|
|
41
|
+
*/
|
|
42
|
+
startSync<TCallback extends (issue: NewActivityWithNotes, ...args: any[]) => any>(authToken: string, projectId: string, callback: TCallback, options?: ProjectSyncOptions, ...extraArgs: TCallback extends (issue: any, ...rest: infer R) => any ? R : []): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Setup Linear webhook for real-time updates
|
|
45
|
+
*/
|
|
46
|
+
private setupLinearWebhook;
|
|
47
|
+
/**
|
|
48
|
+
* Initialize batch sync process
|
|
49
|
+
*/
|
|
50
|
+
private startBatchSync;
|
|
51
|
+
/**
|
|
52
|
+
* Process a batch of issues
|
|
53
|
+
*/
|
|
54
|
+
private syncBatch;
|
|
55
|
+
/**
|
|
56
|
+
* Convert a Linear issue to a Plot Activity
|
|
57
|
+
*/
|
|
58
|
+
private convertIssueToActivity;
|
|
59
|
+
/**
|
|
60
|
+
* Update issue with new values
|
|
61
|
+
*
|
|
62
|
+
* @param authToken - Authorization token
|
|
63
|
+
* @param update - ActivityUpdate with changed fields
|
|
64
|
+
*/
|
|
65
|
+
updateIssue(authToken: string, update: import("@plotday/twister").ActivityUpdate): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Add a comment to a Linear issue
|
|
68
|
+
*
|
|
69
|
+
* @param authToken - Authorization token
|
|
70
|
+
* @param issueId - Linear issue ID
|
|
71
|
+
* @param body - Comment text (markdown supported)
|
|
72
|
+
*/
|
|
73
|
+
addIssueComment(authToken: string, issueId: string, body: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Verify Linear webhook signature
|
|
76
|
+
* Linear uses HMAC-SHA256 with the webhook secret
|
|
77
|
+
*/
|
|
78
|
+
private verifyLinearSignature;
|
|
79
|
+
/**
|
|
80
|
+
* Handle incoming webhook events from Linear
|
|
81
|
+
*/
|
|
82
|
+
private onWebhook;
|
|
83
|
+
/**
|
|
84
|
+
* Stop syncing a Linear team
|
|
85
|
+
*/
|
|
86
|
+
stopSync(authToken: string, projectId: string): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
export default Linear;
|
package/dist/linear.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { LinearClient } from "@linear/sdk";
|
|
2
|
+
import { ActivityType, } from "@plotday/twister";
|
|
3
|
+
import { Tool } from "@plotday/twister/tool";
|
|
4
|
+
import { Callbacks } from "@plotday/twister/tools/callbacks";
|
|
5
|
+
import { AuthLevel, AuthProvider, Integrations, } from "@plotday/twister/tools/integrations";
|
|
6
|
+
import { Network } from "@plotday/twister/tools/network";
|
|
7
|
+
import { ContactAccess, Plot } from "@plotday/twister/tools/plot";
|
|
8
|
+
import { Tasks } from "@plotday/twister/tools/tasks";
|
|
9
|
+
/**
|
|
10
|
+
* Linear project management tool
|
|
11
|
+
*
|
|
12
|
+
* Implements the ProjectTool interface for syncing Linear teams and issues
|
|
13
|
+
* with Plot activities.
|
|
14
|
+
*/
|
|
15
|
+
export class Linear extends Tool {
|
|
16
|
+
build(build) {
|
|
17
|
+
return {
|
|
18
|
+
integrations: build(Integrations),
|
|
19
|
+
network: build(Network, { urls: ["https://api.linear.app/*"] }),
|
|
20
|
+
callbacks: build(Callbacks),
|
|
21
|
+
tasks: build(Tasks),
|
|
22
|
+
plot: build(Plot, { contact: { access: ContactAccess.Write } }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create Linear API client with auth token
|
|
27
|
+
*/
|
|
28
|
+
async getClient(authToken) {
|
|
29
|
+
const authorization = await this.get(`authorization:${authToken}`);
|
|
30
|
+
if (!authorization) {
|
|
31
|
+
throw new Error("Authorization no longer available");
|
|
32
|
+
}
|
|
33
|
+
const token = await this.tools.integrations.get(authorization);
|
|
34
|
+
if (!token) {
|
|
35
|
+
throw new Error("Authorization no longer available");
|
|
36
|
+
}
|
|
37
|
+
return new LinearClient({ accessToken: token.token });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Request Linear OAuth authorization
|
|
41
|
+
*/
|
|
42
|
+
async requestAuth(callback, ...extraArgs) {
|
|
43
|
+
const linearScopes = ["read", "write"];
|
|
44
|
+
// Generate opaque token for authorization
|
|
45
|
+
const authToken = crypto.randomUUID();
|
|
46
|
+
const callbackToken = await this.tools.callbacks.createFromParent(callback, ...extraArgs);
|
|
47
|
+
// Request auth and return the activity link
|
|
48
|
+
return await this.tools.integrations.request({
|
|
49
|
+
provider: AuthProvider.Linear,
|
|
50
|
+
level: AuthLevel.User,
|
|
51
|
+
scopes: linearScopes,
|
|
52
|
+
}, this.onAuthSuccess, authToken, callbackToken);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Handle successful OAuth authorization
|
|
56
|
+
*/
|
|
57
|
+
async onAuthSuccess(authorization, authToken, callbackToken) {
|
|
58
|
+
// Store authorization for later use
|
|
59
|
+
await this.set(`authorization:${authToken}`, authorization);
|
|
60
|
+
// Execute the callback with the auth token
|
|
61
|
+
await this.run(callbackToken, { authToken });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get list of Linear teams (projects)
|
|
65
|
+
*/
|
|
66
|
+
async getProjects(authToken) {
|
|
67
|
+
const client = await this.getClient(authToken);
|
|
68
|
+
const teams = await client.teams();
|
|
69
|
+
return teams.nodes.map((team) => ({
|
|
70
|
+
id: team.id,
|
|
71
|
+
name: team.name,
|
|
72
|
+
description: team.description || null,
|
|
73
|
+
key: team.key,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Start syncing issues from a Linear team
|
|
78
|
+
*/
|
|
79
|
+
async startSync(authToken, projectId, callback, options, ...extraArgs) {
|
|
80
|
+
// Setup webhook for real-time updates
|
|
81
|
+
await this.setupLinearWebhook(authToken, projectId);
|
|
82
|
+
// Store callback for webhook processing
|
|
83
|
+
const callbackToken = await this.tools.callbacks.createFromParent(callback, ...extraArgs);
|
|
84
|
+
await this.set(`callback_${projectId}`, callbackToken);
|
|
85
|
+
// Start initial batch sync
|
|
86
|
+
await this.startBatchSync(authToken, projectId, options);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Setup Linear webhook for real-time updates
|
|
90
|
+
*/
|
|
91
|
+
async setupLinearWebhook(authToken, projectId) {
|
|
92
|
+
try {
|
|
93
|
+
const client = await this.getClient(authToken);
|
|
94
|
+
// Create webhook URL first (Linear requires valid URL at creation time)
|
|
95
|
+
const webhookUrl = await this.tools.network.createWebhook({
|
|
96
|
+
callback: this.onWebhook,
|
|
97
|
+
extraArgs: [projectId, authToken],
|
|
98
|
+
});
|
|
99
|
+
// Skip webhook setup for localhost (development mode)
|
|
100
|
+
if (webhookUrl.includes("localhost") ||
|
|
101
|
+
webhookUrl.includes("127.0.0.1")) {
|
|
102
|
+
console.log("Skipping webhook setup for localhost URL:", webhookUrl);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Create webhook in Linear with the actual URL
|
|
106
|
+
const webhookPayload = await client.createWebhook({
|
|
107
|
+
url: webhookUrl,
|
|
108
|
+
teamId: projectId,
|
|
109
|
+
resourceTypes: ["Issue", "Comment"],
|
|
110
|
+
});
|
|
111
|
+
// Extract and store webhook ID and secret
|
|
112
|
+
const webhook = await webhookPayload.webhook;
|
|
113
|
+
if (webhook?.id) {
|
|
114
|
+
await this.set(`webhook_id_${projectId}`, webhook.id);
|
|
115
|
+
}
|
|
116
|
+
if (webhook?.secret) {
|
|
117
|
+
await this.set(`webhook_secret_${projectId}`, webhook.secret);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.warn("Failed to set up Linear webhook, continuing with sync:", error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Initialize batch sync process
|
|
126
|
+
*/
|
|
127
|
+
async startBatchSync(authToken, projectId, options) {
|
|
128
|
+
// Initialize sync state
|
|
129
|
+
await this.set(`sync_state_${projectId}`, {
|
|
130
|
+
after: null,
|
|
131
|
+
batchNumber: 1,
|
|
132
|
+
issuesProcessed: 0,
|
|
133
|
+
initialSync: true,
|
|
134
|
+
});
|
|
135
|
+
// Queue first batch
|
|
136
|
+
const batchCallback = await this.callback(this.syncBatch, authToken, projectId, options);
|
|
137
|
+
await this.tools.tasks.runTask(batchCallback);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Process a batch of issues
|
|
141
|
+
*/
|
|
142
|
+
async syncBatch(authToken, projectId, options) {
|
|
143
|
+
const state = await this.get(`sync_state_${projectId}`);
|
|
144
|
+
if (!state) {
|
|
145
|
+
throw new Error(`Sync state not found for project ${projectId}`);
|
|
146
|
+
}
|
|
147
|
+
// Retrieve callback token from storage
|
|
148
|
+
const callbackToken = await this.get(`callback_${projectId}`);
|
|
149
|
+
if (!callbackToken) {
|
|
150
|
+
throw new Error(`Callback token not found for project ${projectId}`);
|
|
151
|
+
}
|
|
152
|
+
const client = await this.getClient(authToken);
|
|
153
|
+
const team = await client.team(projectId);
|
|
154
|
+
// Build filter
|
|
155
|
+
const filter = {};
|
|
156
|
+
if (options?.timeMin) {
|
|
157
|
+
filter.createdAt = { gte: options.timeMin };
|
|
158
|
+
}
|
|
159
|
+
// Fetch batch of issues (50 at a time)
|
|
160
|
+
const issuesConnection = await team.issues({
|
|
161
|
+
first: 50,
|
|
162
|
+
after: state.after || undefined,
|
|
163
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
164
|
+
});
|
|
165
|
+
// Process each issue
|
|
166
|
+
for (const issue of issuesConnection.nodes) {
|
|
167
|
+
const activityWithNotes = await this.convertIssueToActivity(issue, projectId);
|
|
168
|
+
// Set unread based on sync type (false for initial sync to avoid notification overload)
|
|
169
|
+
activityWithNotes.unread = !state.initialSync;
|
|
170
|
+
// Execute the callback using the callback token
|
|
171
|
+
await this.tools.callbacks.run(callbackToken, activityWithNotes);
|
|
172
|
+
}
|
|
173
|
+
// Check if more pages
|
|
174
|
+
if (issuesConnection.pageInfo.hasNextPage) {
|
|
175
|
+
await this.set(`sync_state_${projectId}`, {
|
|
176
|
+
after: issuesConnection.pageInfo.endCursor,
|
|
177
|
+
batchNumber: state.batchNumber + 1,
|
|
178
|
+
issuesProcessed: state.issuesProcessed + issuesConnection.nodes.length,
|
|
179
|
+
initialSync: state.initialSync,
|
|
180
|
+
});
|
|
181
|
+
// Queue next batch
|
|
182
|
+
const nextBatch = await this.callback(this.syncBatch, authToken, projectId, options);
|
|
183
|
+
await this.tools.tasks.runTask(nextBatch);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Initial sync is complete - cleanup sync state
|
|
187
|
+
await this.clear(`sync_state_${projectId}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Convert a Linear issue to a Plot Activity
|
|
192
|
+
*/
|
|
193
|
+
async convertIssueToActivity(issue, projectId) {
|
|
194
|
+
const state = await issue.state;
|
|
195
|
+
const assignee = await issue.assignee;
|
|
196
|
+
const comments = await issue.comments();
|
|
197
|
+
// Build notes array: description + comments
|
|
198
|
+
const notes = [];
|
|
199
|
+
if (issue.description) {
|
|
200
|
+
notes.push({ content: issue.description });
|
|
201
|
+
}
|
|
202
|
+
for (const comment of comments.nodes) {
|
|
203
|
+
notes.push({ content: comment.body });
|
|
204
|
+
}
|
|
205
|
+
// Ensure at least one note exists
|
|
206
|
+
if (notes.length === 0) {
|
|
207
|
+
notes.push({ content: "" });
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
type: ActivityType.Action,
|
|
211
|
+
title: issue.title,
|
|
212
|
+
source: `linear:issue:${projectId}:${issue.id}`,
|
|
213
|
+
doneAt: state?.name === "Done" || state?.name === "Completed"
|
|
214
|
+
? new Date()
|
|
215
|
+
: null,
|
|
216
|
+
notes,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Update issue with new values
|
|
221
|
+
*
|
|
222
|
+
* @param authToken - Authorization token
|
|
223
|
+
* @param update - ActivityUpdate with changed fields
|
|
224
|
+
*/
|
|
225
|
+
async updateIssue(authToken, update) {
|
|
226
|
+
// Extract Linear issue ID from source
|
|
227
|
+
const issueId = update.source?.split(":").pop();
|
|
228
|
+
if (!issueId) {
|
|
229
|
+
throw new Error("Invalid source format for Linear issue");
|
|
230
|
+
}
|
|
231
|
+
const client = await this.getClient(authToken);
|
|
232
|
+
const issue = await client.issue(issueId);
|
|
233
|
+
const updateFields = {};
|
|
234
|
+
// Handle title
|
|
235
|
+
if (update.title !== undefined) {
|
|
236
|
+
updateFields.title = update.title;
|
|
237
|
+
}
|
|
238
|
+
// Handle assignee
|
|
239
|
+
if (update.assignee !== undefined) {
|
|
240
|
+
updateFields.assigneeId = update.assignee?.id || null;
|
|
241
|
+
}
|
|
242
|
+
// Handle state based on start + doneAt combination
|
|
243
|
+
if (update.start !== undefined || update.doneAt !== undefined) {
|
|
244
|
+
const team = await issue.team;
|
|
245
|
+
if (team) {
|
|
246
|
+
const states = await team.states();
|
|
247
|
+
let targetState;
|
|
248
|
+
// Determine target state based on combination
|
|
249
|
+
if (update.doneAt !== undefined && update.doneAt !== null) {
|
|
250
|
+
// Completed
|
|
251
|
+
targetState = states.nodes.find((s) => s.name === "Done" ||
|
|
252
|
+
s.name === "Completed" ||
|
|
253
|
+
s.type === "completed");
|
|
254
|
+
}
|
|
255
|
+
else if (update.start !== undefined && update.start !== null) {
|
|
256
|
+
// In Progress (has start date, not done)
|
|
257
|
+
targetState = states.nodes.find((s) => s.name === "In Progress" || s.type === "started");
|
|
258
|
+
}
|
|
259
|
+
else if ((update.start !== undefined && update.start === null) ||
|
|
260
|
+
(update.doneAt !== undefined && update.doneAt === null)) {
|
|
261
|
+
// Backlog/Todo (no start date, not done)
|
|
262
|
+
targetState = states.nodes.find((s) => s.name === "Todo" ||
|
|
263
|
+
s.name === "Backlog" ||
|
|
264
|
+
s.type === "unstarted");
|
|
265
|
+
}
|
|
266
|
+
if (targetState) {
|
|
267
|
+
updateFields.stateId = targetState.id;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Apply updates if any fields changed
|
|
272
|
+
if (Object.keys(updateFields).length > 0) {
|
|
273
|
+
await client.updateIssue(issueId, updateFields);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Add a comment to a Linear issue
|
|
278
|
+
*
|
|
279
|
+
* @param authToken - Authorization token
|
|
280
|
+
* @param issueId - Linear issue ID
|
|
281
|
+
* @param body - Comment text (markdown supported)
|
|
282
|
+
*/
|
|
283
|
+
async addIssueComment(authToken, issueId, body) {
|
|
284
|
+
const client = await this.getClient(authToken);
|
|
285
|
+
await client.createComment({
|
|
286
|
+
issueId,
|
|
287
|
+
body,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Verify Linear webhook signature
|
|
292
|
+
* Linear uses HMAC-SHA256 with the webhook secret
|
|
293
|
+
*/
|
|
294
|
+
async verifyLinearSignature(signature, rawBody, secret, webhookTimestamp) {
|
|
295
|
+
if (!signature) {
|
|
296
|
+
console.warn("Linear webhook missing signature header");
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
// Verify timestamp to prevent replay attacks (within 60 seconds)
|
|
300
|
+
const currentTime = Date.now();
|
|
301
|
+
const timeDiff = Math.abs(currentTime - webhookTimestamp);
|
|
302
|
+
if (timeDiff > 60000) {
|
|
303
|
+
console.warn(`Linear webhook timestamp too old: ${timeDiff}ms (max 60000ms)`);
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
// Compute HMAC-SHA256 signature
|
|
307
|
+
const encoder = new TextEncoder();
|
|
308
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
309
|
+
const signatureBytes = await crypto.subtle.sign("HMAC", key, encoder.encode(rawBody));
|
|
310
|
+
// Convert to hex string
|
|
311
|
+
const expectedSignature = Array.from(new Uint8Array(signatureBytes))
|
|
312
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
313
|
+
.join("");
|
|
314
|
+
// Constant-time comparison
|
|
315
|
+
return signature === expectedSignature;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Handle incoming webhook events from Linear
|
|
319
|
+
*/
|
|
320
|
+
async onWebhook(request, projectId, authToken, webhookSecret) {
|
|
321
|
+
const payload = request.body;
|
|
322
|
+
// Verify webhook signature
|
|
323
|
+
// Linear sends Linear-Signature header (not X-Linear-Signature)
|
|
324
|
+
const secret = webhookSecret || (await this.get(`webhook_secret_${projectId}`));
|
|
325
|
+
if (secret && request.rawBody) {
|
|
326
|
+
const signature = request.headers["linear-signature"];
|
|
327
|
+
const isValid = await this.verifyLinearSignature(signature, request.rawBody, secret, payload.webhookTimestamp);
|
|
328
|
+
if (!isValid) {
|
|
329
|
+
console.warn("Linear webhook signature verification failed");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else if (!secret) {
|
|
334
|
+
console.warn("Linear webhook secret not found, skipping verification");
|
|
335
|
+
}
|
|
336
|
+
if (payload.type === "Issue" || payload.type === "Comment") {
|
|
337
|
+
const callbackToken = await this.get(`callback_${projectId}`);
|
|
338
|
+
if (!callbackToken)
|
|
339
|
+
return;
|
|
340
|
+
const client = await this.getClient(authToken);
|
|
341
|
+
const issue = await client.issue(payload.data.id);
|
|
342
|
+
const activityWithNotes = await this.convertIssueToActivity(issue, projectId);
|
|
343
|
+
// Webhooks are incremental updates - mark as unread
|
|
344
|
+
activityWithNotes.unread = true;
|
|
345
|
+
// Execute stored callback
|
|
346
|
+
await this.tools.callbacks.run(callbackToken, activityWithNotes);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Stop syncing a Linear team
|
|
351
|
+
*/
|
|
352
|
+
async stopSync(authToken, projectId) {
|
|
353
|
+
// Remove webhook
|
|
354
|
+
const webhookId = await this.get(`webhook_id_${projectId}`);
|
|
355
|
+
if (webhookId) {
|
|
356
|
+
try {
|
|
357
|
+
const client = await this.getClient(authToken);
|
|
358
|
+
await client.deleteWebhook(webhookId);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
console.warn("Failed to delete Linear webhook:", error);
|
|
362
|
+
}
|
|
363
|
+
await this.clear(`webhook_id_${projectId}`);
|
|
364
|
+
}
|
|
365
|
+
// Cleanup webhook secret
|
|
366
|
+
await this.clear(`webhook_secret_${projectId}`);
|
|
367
|
+
// Cleanup callback
|
|
368
|
+
const callbackToken = await this.get(`callback_${projectId}`);
|
|
369
|
+
if (callbackToken) {
|
|
370
|
+
await this.deleteCallback(callbackToken);
|
|
371
|
+
await this.clear(`callback_${projectId}`);
|
|
372
|
+
}
|
|
373
|
+
// Cleanup sync state
|
|
374
|
+
await this.clear(`sync_state_${projectId}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
export default Linear;
|
|
378
|
+
//# sourceMappingURL=linear.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear.js","sourceRoot":"","sources":["../src/linear.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plotday/tool-linear",
|
|
3
|
+
"displayName": "Linear",
|
|
4
|
+
"description": "Sync with Linear project management",
|
|
5
|
+
"author": "Plot <team@plot.day> (https://plot.day)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"version": "0.2.0",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"@plotday/source": "./src/index.ts",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@linear/sdk": "^27.0.0",
|
|
25
|
+
"@plotday/twister": "^0.26.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/plotday/plot.git",
|
|
33
|
+
"directory": "tools/linear"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://plot.day",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/plotday/plot/issues"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"plot",
|
|
41
|
+
"tool",
|
|
42
|
+
"linear",
|
|
43
|
+
"project-management"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"clean": "rm -rf dist"
|
|
51
|
+
}
|
|
52
|
+
}
|