@chat-adapter/linear 4.8.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 +9 -0
- package/README.md +254 -0
- package/dist/index.d.ts +334 -0
- package/dist/index.js +727 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vercel, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# @chat-adapter/linear
|
|
2
|
+
|
|
3
|
+
Linear adapter for the [chat](https://github.com/vercel-labs/chat) SDK. Enables bots to respond to @mentions in Linear issue comment threads.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install chat @chat-adapter/linear
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Chat } from "chat";
|
|
15
|
+
import { createLinearAdapter } from "@chat-adapter/linear";
|
|
16
|
+
import { MemoryState } from "@chat-adapter/state-memory";
|
|
17
|
+
|
|
18
|
+
const chat = new Chat({
|
|
19
|
+
userName: "my-bot",
|
|
20
|
+
adapters: {
|
|
21
|
+
linear: createLinearAdapter({
|
|
22
|
+
apiKey: process.env.LINEAR_API_KEY!,
|
|
23
|
+
webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
|
|
24
|
+
userName: "my-bot",
|
|
25
|
+
logger: console,
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
state: new MemoryState(),
|
|
29
|
+
logger: "info",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Handle @mentions in issue comments
|
|
33
|
+
chat.onNewMention(async (thread, message) => {
|
|
34
|
+
await thread.post("Hello from Linear!");
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
| Option | Required | Description |
|
|
41
|
+
| --------------- | -------- | --------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `apiKey` | Yes\* | [Personal API key](https://linear.app/docs/api-and-webhooks) from Settings > Security & Access |
|
|
43
|
+
| `clientId` | Yes\* | [OAuth app](https://linear.app/developers/oauth-2-0-authentication) client ID |
|
|
44
|
+
| `clientSecret` | Yes\* | OAuth app client secret |
|
|
45
|
+
| `accessToken` | Yes\* | Pre-obtained [OAuth access token](https://linear.app/developers/oauth-2-0-authentication) |
|
|
46
|
+
| `webhookSecret` | Yes | [Webhook signing secret](https://linear.app/developers/webhooks#securing-webhooks) for verification |
|
|
47
|
+
| `userName` | Yes | Bot display name for @mention detection |
|
|
48
|
+
|
|
49
|
+
\*One of: `apiKey`, `clientId`/`clientSecret`, or `accessToken` is required.
|
|
50
|
+
|
|
51
|
+
## Environment Variables
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# API Key auth (simplest)
|
|
55
|
+
LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
|
|
56
|
+
|
|
57
|
+
# OR OAuth app auth (recommended for production)
|
|
58
|
+
LINEAR_CLIENT_ID=your-client-id
|
|
59
|
+
LINEAR_CLIENT_SECRET=your-client-secret
|
|
60
|
+
|
|
61
|
+
# Webhook secret (required for all auth methods)
|
|
62
|
+
LINEAR_WEBHOOK_SECRET=your-webhook-secret
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Linear Setup
|
|
66
|
+
|
|
67
|
+
### Option A: Personal API Key
|
|
68
|
+
|
|
69
|
+
Best for personal projects, testing, or single-workspace bots.
|
|
70
|
+
|
|
71
|
+
1. Go to [Settings > Security & Access](https://linear.app/settings/account/security) in Linear
|
|
72
|
+
2. Scroll to **Personal API keys** and click **Create key**
|
|
73
|
+
3. Select **Only select permissions** and enable:
|
|
74
|
+
- **Create issues** - Create and update issues
|
|
75
|
+
- **Create comments** - Create and update issue comments
|
|
76
|
+
4. Under **Team access**, choose **All teams** or select specific teams
|
|
77
|
+
5. Click **Create** and set `LINEAR_API_KEY` environment variable
|
|
78
|
+
|
|
79
|
+
> **Note:** When using a personal API key, all actions are attributed to you as an individual.
|
|
80
|
+
|
|
81
|
+
### Option B: OAuth Application (Recommended for Apps)
|
|
82
|
+
|
|
83
|
+
Use this if you want the bot to have its **own identity** in Linear (not attributed to you personally), or if you're building a public integration. The adapter handles token management internally -- no need to store tokens.
|
|
84
|
+
|
|
85
|
+
1. Go to [Settings > API > Applications](https://linear.app/settings/api/applications/new) in Linear
|
|
86
|
+
2. Create a new OAuth2 application with your bot's name and icon
|
|
87
|
+
3. **Enable client credentials tokens** in the app settings
|
|
88
|
+
4. Note your **Client ID** and **Client Secret**
|
|
89
|
+
5. Set `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` environment variables
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
createLinearAdapter({
|
|
93
|
+
clientId: process.env.LINEAR_CLIENT_ID!,
|
|
94
|
+
clientSecret: process.env.LINEAR_CLIENT_SECRET!,
|
|
95
|
+
webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
|
|
96
|
+
userName: "my-bot",
|
|
97
|
+
logger: console,
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The adapter uses the [client credentials grant](https://linear.app/developers/oauth-2-0-authentication#client-credentials-tokens) to obtain tokens automatically. Tokens are valid for 30 days and auto-refresh when expired.
|
|
102
|
+
|
|
103
|
+
> **When to use which?**
|
|
104
|
+
>
|
|
105
|
+
> - **API Key** -- Personal projects, testing, single workspace. Actions attributed to you.
|
|
106
|
+
> - **OAuth App** -- Production bots, public integrations, bot has its own identity.
|
|
107
|
+
> - For making the bot `@`-mentionable as an [Agent](https://linear.app/developers/agents), see the [OAuth setup guide](#oauth-setup-guide) below.
|
|
108
|
+
|
|
109
|
+
### Webhook Setup
|
|
110
|
+
|
|
111
|
+
See the [Linear Webhooks documentation](https://linear.app/developers/webhooks) for detailed instructions.
|
|
112
|
+
|
|
113
|
+
> **Note:** Webhook management requires **workspace admin** access. If you don't see the API settings page, ask a workspace admin to create the webhook for you.
|
|
114
|
+
|
|
115
|
+
1. Go to **Settings > API** in your Linear workspace and click **Create webhook**
|
|
116
|
+
2. Fill in:
|
|
117
|
+
- **Label**: A descriptive name (e.g., "Chat Bot")
|
|
118
|
+
- **URL**: `https://your-domain.com/api/webhooks/linear`
|
|
119
|
+
3. Copy the **Signing secret** and set it as `LINEAR_WEBHOOK_SECRET`
|
|
120
|
+
4. Under **Data change events**, select:
|
|
121
|
+
- **Comments** (required - for issue comments)
|
|
122
|
+
- **Issues** (recommended - for mentions in issue descriptions)
|
|
123
|
+
- **Emoji reactions** (optional - for reaction handling)
|
|
124
|
+
5. Under **Team selection**, choose **All public teams** or a specific team
|
|
125
|
+
6. Click **Create webhook**
|
|
126
|
+
|
|
127
|
+
## Features
|
|
128
|
+
|
|
129
|
+
- Message posting and editing
|
|
130
|
+
- Message deletion
|
|
131
|
+
- [Reaction handling](https://linear.app/docs/comment-on-issues) (add reactions via emoji)
|
|
132
|
+
- Issue comment threads
|
|
133
|
+
- Cards (rendered as [Markdown](https://linear.app/docs/comment-on-issues))
|
|
134
|
+
|
|
135
|
+
## Thread Model
|
|
136
|
+
|
|
137
|
+
Linear has two levels of comment threading:
|
|
138
|
+
|
|
139
|
+
| Type | Description | Thread ID Format |
|
|
140
|
+
| -------------- | --------------------------------------- | -------------------------------- |
|
|
141
|
+
| Issue-level | Top-level comments on an issue | `linear:{issueId}` |
|
|
142
|
+
| Comment thread | Replies nested under a specific comment | `linear:{issueId}:c:{commentId}` |
|
|
143
|
+
|
|
144
|
+
When a user writes a comment, the bot replies **within the same comment thread** (nested under the same card). This matches the expected Linear UX where conversations are grouped.
|
|
145
|
+
|
|
146
|
+
Example thread IDs:
|
|
147
|
+
|
|
148
|
+
- `linear:2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9` (issue-level)
|
|
149
|
+
- `linear:2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9:c:comment-abc123` (comment thread)
|
|
150
|
+
|
|
151
|
+
## Reactions
|
|
152
|
+
|
|
153
|
+
Linear supports standard [emoji reactions](https://linear.app/docs/comment-on-issues) on comments. The adapter maps SDK emoji names to unicode:
|
|
154
|
+
|
|
155
|
+
| SDK Emoji | Linear Emoji |
|
|
156
|
+
| ------------- | ------------ |
|
|
157
|
+
| `thumbs_up` | 👍 |
|
|
158
|
+
| `thumbs_down` | 👎 |
|
|
159
|
+
| `heart` | ❤️ |
|
|
160
|
+
| `fire` | 🔥 |
|
|
161
|
+
| `rocket` | 🚀 |
|
|
162
|
+
| `eyes` | 👀 |
|
|
163
|
+
| `sparkles` | ✨ |
|
|
164
|
+
| `wave` | 👋 |
|
|
165
|
+
|
|
166
|
+
## Limitations
|
|
167
|
+
|
|
168
|
+
- **No typing indicators** - Linear doesn't support typing indicators
|
|
169
|
+
- **No streaming** - Messages posted in full (editing supported for updates)
|
|
170
|
+
- **No DMs** - Linear doesn't have direct messages
|
|
171
|
+
- **No modals** - Linear doesn't support interactive modals
|
|
172
|
+
- **Action buttons** - Rendered as text; use link buttons for clickable actions
|
|
173
|
+
- **Remove reaction** - Requires reaction ID lookup (not directly supported)
|
|
174
|
+
|
|
175
|
+
## Troubleshooting
|
|
176
|
+
|
|
177
|
+
### "Invalid signature" error
|
|
178
|
+
|
|
179
|
+
- Verify `LINEAR_WEBHOOK_SECRET` matches the secret from your webhook configuration
|
|
180
|
+
- Ensure the request body isn't being modified before verification
|
|
181
|
+
- The webhook secret is shown only once at creation - regenerate if lost
|
|
182
|
+
|
|
183
|
+
### Bot not responding to mentions
|
|
184
|
+
|
|
185
|
+
- Verify webhook events are configured with `Comment` resource type
|
|
186
|
+
- Check that the webhook URL is correct and accessible
|
|
187
|
+
- Ensure the `userName` config matches how users mention the bot
|
|
188
|
+
- Check that the webhook is enabled (Linear may auto-disable after repeated failures)
|
|
189
|
+
|
|
190
|
+
### "Webhook expired" error
|
|
191
|
+
|
|
192
|
+
- This means the webhook timestamp is too old (> 5 minutes)
|
|
193
|
+
- Usually indicates a delivery delay or clock skew
|
|
194
|
+
- Check that your server time is synchronized
|
|
195
|
+
|
|
196
|
+
### Rate limiting
|
|
197
|
+
|
|
198
|
+
- Linear API has [rate limits](https://linear.app/developers/graphql#rate-limiting)
|
|
199
|
+
- The SDK handles rate limiting automatically in most cases
|
|
200
|
+
|
|
201
|
+
## OAuth Setup Guide
|
|
202
|
+
|
|
203
|
+
This section is only relevant if you chose [Option B: OAuth Application](#option-b-oauth-application-recommended-for-apps). If you're using a personal API key (Option A), you can skip this entirely.
|
|
204
|
+
|
|
205
|
+
Setting up an OAuth application gives the bot its own identity in Linear rather than acting as your personal account.
|
|
206
|
+
|
|
207
|
+
### 1. Create the OAuth Application
|
|
208
|
+
|
|
209
|
+
1. Go to [Settings > API > Applications](https://linear.app/settings/api/applications/new) in Linear (requires admin)
|
|
210
|
+
2. Fill in:
|
|
211
|
+
- **Application name**: Your bot's name (e.g., "v0") -- this is how it appears in Linear
|
|
212
|
+
- **Application icon**: Upload an icon for the bot
|
|
213
|
+
- **Redirect callback URLs**: Add a placeholder URL (e.g., `https://your-domain.com/callback`) -- not needed for client credentials auth
|
|
214
|
+
3. Click **Create**
|
|
215
|
+
4. **Enable client credentials tokens** in the app settings
|
|
216
|
+
5. Note your **Client ID** and **Client Secret**
|
|
217
|
+
|
|
218
|
+
### 2. Configure the Adapter
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
createLinearAdapter({
|
|
222
|
+
clientId: process.env.LINEAR_CLIENT_ID!,
|
|
223
|
+
clientSecret: process.env.LINEAR_CLIENT_SECRET!,
|
|
224
|
+
webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
|
|
225
|
+
userName: "v0",
|
|
226
|
+
logger: console,
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
That's it. The adapter automatically obtains and refreshes tokens using the [client credentials grant](https://linear.app/developers/oauth-2-0-authentication#client-credentials-tokens). No callback endpoints, no token storage, no refresh logic on your end.
|
|
231
|
+
|
|
232
|
+
### 3. Making the Bot @-Mentionable (Optional)
|
|
233
|
+
|
|
234
|
+
To make the bot appear in Linear's `@` mention dropdown as an [Agent](https://linear.app/developers/agents):
|
|
235
|
+
|
|
236
|
+
1. In your OAuth app settings, enable **Agent session events** under webhooks
|
|
237
|
+
2. Have a workspace admin install the app using `actor=app` with the `app:mentionable` scope:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
https://linear.app/oauth/authorize?
|
|
241
|
+
client_id=YOUR_CLIENT_ID&
|
|
242
|
+
redirect_uri=https://your-domain.com/callback&
|
|
243
|
+
response_type=code&
|
|
244
|
+
scope=read,write,comments:create,app:mentionable&
|
|
245
|
+
actor=app
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Once installed, the bot shows up as a mentionable user in the workspace. See the [Linear Agents docs](https://linear.app/developers/agents) for full details.
|
|
249
|
+
|
|
250
|
+
For more on Linear OAuth, see the [Linear OAuth 2.0 documentation](https://linear.app/developers/oauth-2-0-authentication).
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { Logger, Adapter, ChatInstance, WebhookOptions, AdapterPostableMessage, RawMessage, EmojiValue, FetchOptions, FetchResult, ThreadInfo, Message, FormattedContent } from 'chat';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type definitions for the Linear adapter.
|
|
5
|
+
*
|
|
6
|
+
* Uses types from @linear/sdk wherever possible.
|
|
7
|
+
* Only defines adapter-specific config, thread IDs, and webhook payloads.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base configuration options shared by all auth methods.
|
|
12
|
+
*/
|
|
13
|
+
interface LinearAdapterBaseConfig {
|
|
14
|
+
/** Logger instance for error reporting */
|
|
15
|
+
logger: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* Webhook signing secret for HMAC-SHA256 verification.
|
|
18
|
+
* Found on the webhook detail page in Linear settings.
|
|
19
|
+
*/
|
|
20
|
+
webhookSecret: string;
|
|
21
|
+
/**
|
|
22
|
+
* Bot display name used for @-mention detection.
|
|
23
|
+
* For API key auth, this is typically the user's display name.
|
|
24
|
+
* For OAuth app auth with actor=app, this is the app name.
|
|
25
|
+
*/
|
|
26
|
+
userName: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration using a personal API key.
|
|
30
|
+
* Simplest setup, suitable for personal bots or testing.
|
|
31
|
+
*
|
|
32
|
+
* @see https://linear.app/docs/api-and-webhooks
|
|
33
|
+
*/
|
|
34
|
+
interface LinearAdapterAPIKeyConfig extends LinearAdapterBaseConfig {
|
|
35
|
+
/** Personal API key from Linear Settings > Security & Access */
|
|
36
|
+
apiKey: string;
|
|
37
|
+
accessToken?: never;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Configuration using an OAuth access token (pre-obtained).
|
|
41
|
+
* Use this if you've already obtained an access token through the OAuth flow.
|
|
42
|
+
*
|
|
43
|
+
* @see https://linear.app/developers/oauth-2-0-authentication
|
|
44
|
+
*/
|
|
45
|
+
interface LinearAdapterOAuthConfig extends LinearAdapterBaseConfig {
|
|
46
|
+
/** OAuth access token obtained through the OAuth flow */
|
|
47
|
+
accessToken: string;
|
|
48
|
+
apiKey?: never;
|
|
49
|
+
clientId?: never;
|
|
50
|
+
clientSecret?: never;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Configuration using OAuth client credentials (recommended for apps).
|
|
54
|
+
* The adapter handles token management internally - no need to store tokens.
|
|
55
|
+
*
|
|
56
|
+
* Uses the client_credentials grant type to obtain an app-level token.
|
|
57
|
+
* The token is valid for 30 days and auto-refreshes on 401.
|
|
58
|
+
*
|
|
59
|
+
* @see https://linear.app/developers/oauth-2-0-authentication#client-credentials-tokens
|
|
60
|
+
*/
|
|
61
|
+
interface LinearAdapterAppConfig extends LinearAdapterBaseConfig {
|
|
62
|
+
/** OAuth application client ID */
|
|
63
|
+
clientId: string;
|
|
64
|
+
/** OAuth application client secret */
|
|
65
|
+
clientSecret: string;
|
|
66
|
+
apiKey?: never;
|
|
67
|
+
accessToken?: never;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Linear adapter configuration - API Key, OAuth token, or OAuth App (client credentials).
|
|
71
|
+
*/
|
|
72
|
+
type LinearAdapterConfig = LinearAdapterAPIKeyConfig | LinearAdapterOAuthConfig | LinearAdapterAppConfig;
|
|
73
|
+
/**
|
|
74
|
+
* Decoded thread ID for Linear.
|
|
75
|
+
*
|
|
76
|
+
* Thread types:
|
|
77
|
+
* - Issue-level: Top-level comments on the issue (no commentId)
|
|
78
|
+
* - Comment thread: Replies nested under a specific root comment (has commentId)
|
|
79
|
+
*/
|
|
80
|
+
interface LinearThreadId {
|
|
81
|
+
/** Linear issue UUID */
|
|
82
|
+
issueId: string;
|
|
83
|
+
/**
|
|
84
|
+
* Root comment ID for comment-level threads.
|
|
85
|
+
* If present, this is a comment thread (replies nest under this comment).
|
|
86
|
+
* If absent, this is an issue-level thread (top-level comment).
|
|
87
|
+
*/
|
|
88
|
+
commentId?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Comment data from a webhook payload.
|
|
92
|
+
*
|
|
93
|
+
* Verified against Linear's Webhooks Schema Explorer and
|
|
94
|
+
* example payloads from the official documentation.
|
|
95
|
+
*
|
|
96
|
+
* @see https://linear.app/developers/webhooks#webhook-payload
|
|
97
|
+
*/
|
|
98
|
+
interface LinearCommentData {
|
|
99
|
+
/** Comment UUID */
|
|
100
|
+
id: string;
|
|
101
|
+
/** Comment body in markdown format */
|
|
102
|
+
body: string;
|
|
103
|
+
/** Issue UUID the comment is associated with */
|
|
104
|
+
issueId: string;
|
|
105
|
+
/** User UUID who wrote the comment */
|
|
106
|
+
userId: string;
|
|
107
|
+
/** Parent comment UUID (for nested/threaded replies) */
|
|
108
|
+
parentId?: string;
|
|
109
|
+
/** ISO 8601 creation date */
|
|
110
|
+
createdAt: string;
|
|
111
|
+
/** ISO 8601 last update date */
|
|
112
|
+
updatedAt: string;
|
|
113
|
+
/** Direct URL to the comment */
|
|
114
|
+
url?: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Platform-specific raw message type for Linear.
|
|
118
|
+
*/
|
|
119
|
+
interface LinearRawMessage {
|
|
120
|
+
/** The raw comment data from webhook or API */
|
|
121
|
+
comment: LinearCommentData;
|
|
122
|
+
/** Organization ID from the webhook */
|
|
123
|
+
organizationId?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Linear adapter for chat SDK.
|
|
128
|
+
*
|
|
129
|
+
* Supports comment threads on Linear issues.
|
|
130
|
+
* Authentication via personal API key or OAuth access token.
|
|
131
|
+
*
|
|
132
|
+
* @example API Key auth
|
|
133
|
+
* ```typescript
|
|
134
|
+
* import { Chat } from "chat";
|
|
135
|
+
* import { createLinearAdapter } from "@chat-adapter/linear";
|
|
136
|
+
* import { MemoryState } from "@chat-adapter/state-memory";
|
|
137
|
+
*
|
|
138
|
+
* const chat = new Chat({
|
|
139
|
+
* userName: "my-bot",
|
|
140
|
+
* adapters: {
|
|
141
|
+
* linear: createLinearAdapter({
|
|
142
|
+
* apiKey: process.env.LINEAR_API_KEY!,
|
|
143
|
+
* webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
|
|
144
|
+
* userName: "my-bot",
|
|
145
|
+
* logger: console,
|
|
146
|
+
* }),
|
|
147
|
+
* },
|
|
148
|
+
* state: new MemoryState(),
|
|
149
|
+
* logger: "info",
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* @example OAuth auth
|
|
154
|
+
* ```typescript
|
|
155
|
+
* const chat = new Chat({
|
|
156
|
+
* userName: "my-bot",
|
|
157
|
+
* adapters: {
|
|
158
|
+
* linear: createLinearAdapter({
|
|
159
|
+
* accessToken: process.env.LINEAR_ACCESS_TOKEN!,
|
|
160
|
+
* webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
|
|
161
|
+
* userName: "my-bot",
|
|
162
|
+
* logger: console,
|
|
163
|
+
* }),
|
|
164
|
+
* },
|
|
165
|
+
* state: new MemoryState(),
|
|
166
|
+
* logger: "info",
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare class LinearAdapter implements Adapter<LinearThreadId, LinearRawMessage> {
|
|
171
|
+
readonly name = "linear";
|
|
172
|
+
readonly userName: string;
|
|
173
|
+
private linearClient;
|
|
174
|
+
private webhookSecret;
|
|
175
|
+
private chat;
|
|
176
|
+
private logger;
|
|
177
|
+
private _botUserId;
|
|
178
|
+
private formatConverter;
|
|
179
|
+
private clientCredentials;
|
|
180
|
+
private accessTokenExpiry;
|
|
181
|
+
/** Bot user ID used for self-message detection */
|
|
182
|
+
get botUserId(): string | undefined;
|
|
183
|
+
constructor(config: LinearAdapterConfig);
|
|
184
|
+
initialize(chat: ChatInstance): Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* Fetch a new access token using client credentials grant.
|
|
187
|
+
* The token is valid for 30 days. The adapter auto-refreshes on 401.
|
|
188
|
+
*
|
|
189
|
+
* @see https://linear.app/developers/oauth-2-0-authentication#client-credentials-tokens
|
|
190
|
+
*/
|
|
191
|
+
private refreshClientCredentialsToken;
|
|
192
|
+
/**
|
|
193
|
+
* Ensure the client credentials token is still valid. Refresh if expired.
|
|
194
|
+
*/
|
|
195
|
+
private ensureValidToken;
|
|
196
|
+
/**
|
|
197
|
+
* Handle incoming webhook from Linear.
|
|
198
|
+
*
|
|
199
|
+
* @see https://linear.app/developers/webhooks
|
|
200
|
+
*/
|
|
201
|
+
handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
|
|
202
|
+
/**
|
|
203
|
+
* Verify Linear webhook signature using HMAC-SHA256.
|
|
204
|
+
*
|
|
205
|
+
* @see https://linear.app/developers/webhooks#securing-webhooks
|
|
206
|
+
*/
|
|
207
|
+
private verifySignature;
|
|
208
|
+
/**
|
|
209
|
+
* Handle a new comment created on an issue.
|
|
210
|
+
*
|
|
211
|
+
* Threading logic:
|
|
212
|
+
* - If the comment has a parentId, it's a reply -> thread under the parent (root comment)
|
|
213
|
+
* - If no parentId, this is a root comment -> thread under this comment's own ID
|
|
214
|
+
*/
|
|
215
|
+
private handleCommentCreated;
|
|
216
|
+
/**
|
|
217
|
+
* Handle reaction events (logging only - reactions don't include issueId).
|
|
218
|
+
*/
|
|
219
|
+
private handleReaction;
|
|
220
|
+
/**
|
|
221
|
+
* Build a Message from a Linear comment and actor.
|
|
222
|
+
*/
|
|
223
|
+
private buildMessage;
|
|
224
|
+
/**
|
|
225
|
+
* Post a message to a thread (create a comment on an issue).
|
|
226
|
+
*
|
|
227
|
+
* For comment-level threads, uses parentId to reply under the root comment.
|
|
228
|
+
* For issue-level threads, creates a top-level comment.
|
|
229
|
+
*
|
|
230
|
+
* Uses LinearClient.createComment({ issueId, body, parentId? }).
|
|
231
|
+
* @see https://linear.app/developers/sdk-fetching-and-modifying-data#mutations
|
|
232
|
+
*/
|
|
233
|
+
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<LinearRawMessage>>;
|
|
234
|
+
/**
|
|
235
|
+
* Edit an existing message (update a comment).
|
|
236
|
+
*
|
|
237
|
+
* Uses LinearClient.updateComment(id, { body }).
|
|
238
|
+
* @see https://linear.app/developers/sdk-fetching-and-modifying-data#mutations
|
|
239
|
+
*/
|
|
240
|
+
editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage<LinearRawMessage>>;
|
|
241
|
+
/**
|
|
242
|
+
* Delete a message (delete a comment).
|
|
243
|
+
*
|
|
244
|
+
* Uses LinearClient.deleteComment(id).
|
|
245
|
+
*/
|
|
246
|
+
deleteMessage(_threadId: string, messageId: string): Promise<void>;
|
|
247
|
+
/**
|
|
248
|
+
* Add a reaction to a comment.
|
|
249
|
+
*
|
|
250
|
+
* Uses LinearClient.createReaction({ commentId, emoji }).
|
|
251
|
+
* Linear reactions use emoji strings (unicode).
|
|
252
|
+
*/
|
|
253
|
+
addReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Remove a reaction from a comment.
|
|
256
|
+
*
|
|
257
|
+
* Linear doesn't have a direct "remove reaction by emoji + user" API.
|
|
258
|
+
* Removing requires knowing the reaction ID, which would require fetching
|
|
259
|
+
* the comment's reactions first. This is a known limitation.
|
|
260
|
+
*/
|
|
261
|
+
removeReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
|
|
262
|
+
/**
|
|
263
|
+
* Start typing indicator. Not supported by Linear.
|
|
264
|
+
*/
|
|
265
|
+
startTyping(_threadId: string): Promise<void>;
|
|
266
|
+
/**
|
|
267
|
+
* Fetch messages from a thread.
|
|
268
|
+
*
|
|
269
|
+
* For issue-level threads: fetches all top-level issue comments.
|
|
270
|
+
* For comment-level threads: fetches the root comment and its children (replies).
|
|
271
|
+
*/
|
|
272
|
+
fetchMessages(threadId: string, options?: FetchOptions): Promise<FetchResult<LinearRawMessage>>;
|
|
273
|
+
/**
|
|
274
|
+
* Fetch top-level comments on an issue.
|
|
275
|
+
*/
|
|
276
|
+
private fetchIssueComments;
|
|
277
|
+
/**
|
|
278
|
+
* Fetch a comment thread (root comment + its children/replies).
|
|
279
|
+
*/
|
|
280
|
+
private fetchCommentThread;
|
|
281
|
+
/**
|
|
282
|
+
* Convert an array of Linear SDK Comment objects to Message instances.
|
|
283
|
+
*/
|
|
284
|
+
private commentsToMessages;
|
|
285
|
+
/**
|
|
286
|
+
* Fetch thread info for a Linear issue.
|
|
287
|
+
*/
|
|
288
|
+
fetchThread(threadId: string): Promise<ThreadInfo>;
|
|
289
|
+
/**
|
|
290
|
+
* Encode a Linear thread ID.
|
|
291
|
+
*
|
|
292
|
+
* Formats:
|
|
293
|
+
* - Issue-level: linear:{issueId}
|
|
294
|
+
* - Comment thread: linear:{issueId}:c:{commentId}
|
|
295
|
+
*/
|
|
296
|
+
encodeThreadId(platformData: LinearThreadId): string;
|
|
297
|
+
/**
|
|
298
|
+
* Decode a Linear thread ID.
|
|
299
|
+
*
|
|
300
|
+
* Formats:
|
|
301
|
+
* - Issue-level: linear:{issueId}
|
|
302
|
+
* - Comment thread: linear:{issueId}:c:{commentId}
|
|
303
|
+
*/
|
|
304
|
+
decodeThreadId(threadId: string): LinearThreadId;
|
|
305
|
+
/**
|
|
306
|
+
* Parse platform message format to normalized format.
|
|
307
|
+
*/
|
|
308
|
+
parseMessage(raw: LinearRawMessage): Message<LinearRawMessage>;
|
|
309
|
+
/**
|
|
310
|
+
* Render formatted content to Linear markdown.
|
|
311
|
+
*/
|
|
312
|
+
renderFormatted(content: FormattedContent): string;
|
|
313
|
+
/**
|
|
314
|
+
* Resolve an emoji value to a unicode string.
|
|
315
|
+
* Linear uses standard unicode emoji for reactions.
|
|
316
|
+
*/
|
|
317
|
+
private resolveEmoji;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Factory function to create a Linear adapter.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* const adapter = createLinearAdapter({
|
|
325
|
+
* apiKey: process.env.LINEAR_API_KEY!,
|
|
326
|
+
* webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
|
|
327
|
+
* userName: "my-bot",
|
|
328
|
+
* logger: console,
|
|
329
|
+
* });
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
declare function createLinearAdapter(config: LinearAdapterConfig): LinearAdapter;
|
|
333
|
+
|
|
334
|
+
export { LinearAdapter, type LinearAdapterAPIKeyConfig, type LinearAdapterAppConfig, type LinearAdapterConfig, type LinearAdapterOAuthConfig, type LinearRawMessage, type LinearThreadId, createLinearAdapter };
|